Files
FOSSBilling/tests/Unit/LoadCheckInstallerTest.php
Adam Daley 0117aceb51 Fix exception status-code crash and installer cleanup on default deploys (#4171)
* fix(http): don't crash the exception handler on non-int exception codes

Throwable::getCode() is documented as returning int, but PDOException
sets its internal (untyped) $code property to the driver's SQLSTATE
string. ExceptionResponseFactory::getStatusCode() blindly returned
that value from a method typed to return int, so any exception with a
non-int code (e.g. a PDOException raised during session handling)
crashed the exception handler itself with an unrelated TypeError
instead of surfacing the real error.

* fix(update): remove the install directory on default, unconfigured deploys

The install/ directory deletion (in both checkInstaller() and
UpdateFinalization) required an explicit APP_ENV=prod environment
variable. But nothing in the actual shipped deployment path -- the
official Dockerfile, or a typical shared-hosting unzip-and-run install
-- ever sets APP_ENV, so the installer's own completion screen ('The
./install/ folder is normally deleted automatically') was untrue for
the common case: cleanup silently never ran.

Flip the policy to match Environment::isProduction(), which already
treats an unset/unrecognized APP_ENV as production (the ambiguous
default used everywhere else in the app). The install directory is now
removed unless the environment is explicitly dev or test, or DEBUG is
enabled -- so local/CI checkouts keep the installer (and the Pest suite,
which reads install/sql/*.sql directly from disk, keeps working) while
a default production deploy gets cleaned up as documented.
2026-08-16 10:32:11 +01:00

59 lines
2.3 KiB
PHP

<?php
declare(strict_types=1);
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Path;
/**
* Stubs the global $filesystem used by checkInstaller() so the test never touches real
* disk. The installer entry point is reported as missing so the exception-throwing branch,
* which reads the real config via Config::getProperty(), is skipped.
*/
function runCheckInstallerWithStubbedFilesystem(bool $expectRemoval): void
{
$configPath = PATH_CONFIG;
$installerEntryPoint = Path::join('install', 'install.php');
$installDir = Path::normalize('install');
$mock = Mockery::mock(Filesystem::class);
$mock->shouldReceive('exists')->with($configPath)->andReturn(true);
$mock->shouldReceive('exists')->with($installerEntryPoint)->andReturn(false);
$mock->shouldReceive('exists')->with($installDir)->andReturn(true);
if ($expectRemoval) {
$mock->shouldReceive('remove')->once()->with('install');
} else {
$mock->shouldNotReceive('remove');
}
$previousFilesystem = $GLOBALS['filesystem'] ?? null;
$GLOBALS['filesystem'] = $mock;
ini_set('error_log', '/dev/null');
try {
checkInstaller();
} finally {
$GLOBALS['filesystem'] = $previousFilesystem;
// Keep the suite-wide test logging policy after exercising the production cleanup path.
ini_set('error_log', '/dev/null');
}
}
test('checkInstaller deletes the install directory when APP_ENV is unset', function (): void {
withAppEnv(null, fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: true));
});
test('checkInstaller deletes the install directory when APP_ENV holds an unrecognized value', function (): void {
withAppEnv('staging', fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: true));
});
test('checkInstaller does not delete the install directory when APP_ENV is dev or test', function (): void {
withAppEnv('dev', fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: false));
withAppEnv('test', fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: false));
});
test('checkInstaller deletes the install directory when APP_ENV is explicitly prod', function (): void {
withAppEnv('prod', fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: true));
});