tests/Module/System/InstallationHelperTest.php
Rama develop · réplica de lectura del repositorio original.
<?php
declare(strict_types=1);
/**
* vim:set softtabstop=4 shiftwidth=4 expandtab:
*
* LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
* Copyright Ampache.org, 2001-2026
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Ampache\Module\System;
use Ampache\Config\AmpConfig;
use Ampache\MockeryTestCase;
use Ampache\Module\Playback\Stream;
use Ampache\Module\System\Update\UpdaterInterface;
use Ampache\Repository\UpdateInfoRepositoryInterface;
use Override;
use PHPUnit\Framework\Attributes\DataProvider;
class InstallationHelperTest extends MockeryTestCase
{
private string $configFile = '';
private string $file = '';
private InstallationHelper $subject;
/**
* @return array<string, array{array<string, string>, string}>
*/
public static function sendFullStreamProvider(): array
{
return [
'old default from before version 101' => [['config_version' => '100', 'send_full_stream' => 'webplayer'], ';send_full_stream = "webplayer"'],
'other value from before version 101' => [['config_version' => '100', 'send_full_stream' => 'api'], 'send_full_stream = "api"'],
'webplayer chosen from version 101' => [['config_version' => '101', 'send_full_stream' => 'webplayer'], 'send_full_stream = "webplayer"'],
];
}
public function testAnAlreadyPrefixedTargetIsNotPrefixedTwice(): void
{
$rules = 'RewriteRule ^image\.php$ /ampache/image.php [R=302,L]';
self::assertSame($rules, $this->subject->install_check_rewrite_rules($this->writeRules($rules), '/ampache', true));
}
public function testFindConfigSyntaxIssueIgnoresAProperlyEscapedQuote(): void
{
$file = $this->writeConfig(implode("\n", [
'a = "b"',
'foo = "va\"lue"',
'; a comment carrying an "unbalanced quote is never the value itself',
]));
self::assertNull($this->subject->findConfigSyntaxIssue($file));
}
/**
* The offending line is the one PHP's own parser never reports: an unclosed quote makes it keep scanning
* and blame whatever later line it finally trips on, many lines further down the file.
*/
public function testFindConfigSyntaxIssueReportsTheUnclosedLineNotTheOneParseIniFileBlames(): void
{
$file = $this->writeConfig(implode("\n", [
'a = "b"',
'foo = "va"lue"',
'c = "d"',
'; DEFAULT: "false"',
]));
$issue = $this->subject->findConfigSyntaxIssue($file);
self::assertNotNull($issue);
self::assertSame(2, $issue['line']);
self::assertSame('foo = "va"lue"', $issue['content']);
}
public function testFindConfigSyntaxIssueReturnsNullForANonexistentFile(): void
{
self::assertNull($this->subject->findConfigSyntaxIssue(sys_get_temp_dir() . '/nonexistent-ampache.cfg.php'));
}
/**
* A '-' substitution means the rule only sets flags. Prefixing it produces '/ampache-', which is
* a path no request matches, so the rule silently stops applying on a subdirectory install.
*/
public function testFlagOnlyRulesKeepTheirSubstitution(): void
{
$rules = implode("\n", [
'RewriteRule ^(bin|config|vendor)(/|$) - [F,L]',
'RewriteRule (^|/)\. - [F,L]',
'RewriteRule (.*) - [R=400,L]',
]);
self::assertSame(
$rules,
$this->subject->install_check_rewrite_rules($this->writeRules($rules), '/ampache', true)
);
}
/**
* @param array<string, string> $current
*/
#[DataProvider('sendFullStreamProvider')]
public function testGenerateConfigOnlyDropsTheRetiredSendFullStreamDefault(array $current, string $expected): void
{
$lines = array_values(array_filter(
explode("\n", $this->subject->generate_config($current)),
static fn(string $line): bool => str_contains($line, 'send_full_stream =')
));
self::assertSame([$expected], $lines);
}
/**
* The 'transcode' preference (the opt-in switch for transcoding) defaults to 'default' for every user,
* not 'never', so a template that marks common lossless formats 'required' would forbid native streaming
* of them for everyone the moment a transcoder is picked during install, without any further opt-in.
*/
public function testInstallConfigTranscodeModeKeepsNativeStreamingAvailable(): void
{
$this->subject->install_config_transcode_mode('ffmpeg');
self::assertSame('allowed', AmpConfig::get('transcode_flac'));
self::assertSame('allowed', AmpConfig::get('transcode_m4a'));
self::assertSame('allowed', AmpConfig::get('transcode_mpc'));
self::assertSame('allowed', AmpConfig::get('transcode_wav'));
AmpConfig::set('transcode_player_webplayer_flac', '', true);
AmpConfig::set('encode_player_webplayer_target', '', true);
self::assertSame(['native', 'transcode'], Stream::get_stream_types_for_type('flac', 'webplayer'));
}
/**
* Invalid database settings must fail before anything is echoed, the same as an unwritable rewrite file,
* so a download request that never actually downloads still leaves the caller able to show the error.
*/
public function testInstallCreateConfigEchoesNothingWhenValidationFails(): void
{
AmpConfig::set('database_username', '', true);
AmpConfig::set('database_password', '', true);
AmpConfig::set('database_hostname', 'localhost', true);
ob_start();
$result = $this->subject->install_create_config(true);
$output = ob_get_clean();
self::assertFalse($result);
self::assertSame('', $output);
}
/**
* A failed validation must echo nothing, so the caller can safely fall through to the normal error page
* instead of returning a blank response as though a file had already been streamed to the browser.
*/
public function testInstallRewriteRulesEchoesNothingWhenValidationFails(): void
{
ob_start();
$result = $this->subject->install_rewrite_rules(sys_get_temp_dir() . '/nonexistent-htaccess', '/ampache', true);
$output = ob_get_clean();
self::assertFalse($result);
self::assertSame('', $output);
}
/**
* A download that actually streamed the file must report success, not just "not a write failure". The
* install page used the return value to decide whether to render itself afterwards; a false here would
* make it treat an already-sent download the same as a validation failure that echoed nothing at all.
*/
public function testInstallRewriteRulesReportsSuccessAfterStreamingADownload(): void
{
$file = $this->writeRules('RewriteRule ^image\.php$ /image.php [R=302,L]');
copy($file, $file . '.dist');
ob_start();
$result = $this->subject->install_rewrite_rules($file, '/ampache', true);
$output = ob_get_clean();
self::assertTrue($result);
self::assertStringContainsString('/ampache/image.php', $output);
}
public function testRealTargetsGainTheWebPath(): void
{
self::assertSame(
'RewriteRule ^image\.php$ /ampache/image.php?action=show_user_avatar [R=302,L]',
$this->subject->install_check_rewrite_rules(
$this->writeRules('RewriteRule ^image\.php$ /image.php?action=show_user_avatar [R=302,L]'),
'/ampache',
true
)
);
}
public function testShippedRestRulesValidateAgainstTheirOwnDist(): void
{
$dist = __DIR__ . '/../../../public/rest/.htaccess.dist';
$file = $this->writeRules((string) file_get_contents($dist));
copy($dist, $file . '.dist');
self::assertTrue($this->subject->install_check_rewrite_rules($file, ''));
}
/**
* A file left over from an older release keeps the right web path on the rules it does have, so the prefix
* test alone reports it as configured while every rule added since is missing.
*/
public function testValidationFailsForAFileMissingRulesTheDistDeclares(): void
{
$file = $this->writeRules('RewriteRule ^old\.php$ /ampache/old.php [L]');
file_put_contents(
$file . '.dist',
implode("\n", [
'RewriteRule ^old\.php$ /old.php [L]',
'RewriteRule ^new\.php$ /new.php [L]',
])
);
self::assertFalse($this->subject->install_check_rewrite_rules($file, '/ampache'));
}
public function testValidationFailsForAnUnprefixedTarget(): void
{
self::assertFalse(
$this->subject->install_check_rewrite_rules($this->writeRules('RewriteRule ^image\.php$ /image.php [R=302,L]'), '/ampache')
);
}
public function testValidationFailsWhenTheFileHasNotBeenWrittenYet(): void
{
self::assertFalse(
$this->subject->install_check_rewrite_rules(sys_get_temp_dir() . '/nonexistent-htaccess', '')
);
}
public function testValidationPassesForAFileCarryingEveryDistRule(): void
{
$file = $this->writeRules(
implode("\n", [
'RewriteRule ^old\.php$ /ampache/old.php [L]',
'RewriteRule ^new\.php$ /ampache/new.php [L]',
])
);
file_put_contents(
$file . '.dist',
implode("\n", [
'RewriteRule ^old\.php$ /old.php [L]',
'RewriteRule ^new\.php$ /new.php [L]',
])
);
self::assertTrue($this->subject->install_check_rewrite_rules($file, '/ampache'));
}
/**
* The same rules read in validation mode, which is what the install page reports as "configured?"
*/
public function testValidationPassesForFlagOnlyRules(): void
{
self::assertTrue(
$this->subject->install_check_rewrite_rules($this->writeRules('RewriteRule (.*) - [R=400,L]'), '/ampache')
);
}
#[Override]
protected function setUp(): void
{
$this->subject = new InstallationHelper(
$this->mock(UpdaterInterface::class),
$this->mock(UpdateInfoRepositoryInterface::class),
);
// AmpError::$errors is static and outlives a test, so a prior test's 'general' error (if any) makes
// AmpError::add() append rather than set; seed the session key it would append onto either way.
$_SESSION = ['errors' => ['general' => '']];
}
#[Override]
protected function tearDown(): void
{
foreach ([$this->file, $this->file . '.dist'] as $path) {
if ($this->file !== '' && file_exists($path)) {
unlink($path);
}
}
if ($this->configFile !== '' && file_exists($this->configFile)) {
unlink($this->configFile);
}
}
private function writeConfig(string $contents): string
{
$this->configFile = (string) tempnam(sys_get_temp_dir(), 'ampachecfg');
file_put_contents($this->configFile, $contents);
return $this->configFile;
}
private function writeRules(string $rules): string
{
$this->file = (string) tempnam(sys_get_temp_dir(), 'htaccess');
file_put_contents($this->file, $rules);
return $this->file;
}
}