123 lines
6.0 KiB
PHP
123 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace App\Tests;
|
|
|
|
use App\Ops\Environment;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Symfony\Component\Dotenv\Dotenv;
|
|
|
|
final class EnvironmentTest extends TestCase
|
|
{
|
|
private string $root;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->root = sys_get_temp_dir().'/kant-environment-'.bin2hex(random_bytes(6));
|
|
mkdir($this->root);
|
|
file_put_contents($this->root.'/.env.example', "APP_ENV=prod\nDATABASE_URL=sqlite:///default.db\nMAILER_DSN=null://null\n");
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->root, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST);
|
|
foreach ($files as $file) {
|
|
$file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
|
|
}
|
|
rmdir($this->root);
|
|
}
|
|
|
|
public function testNewInstallationUsesOnlyEnvAndKeepsSecretAcrossRequests(): void
|
|
{
|
|
Environment::prepare($this->root);
|
|
$secret = Environment::ensureSecret($this->root);
|
|
Environment::write($this->root, ['DATABASE_URL' => 'sqlite:///customer.db']);
|
|
Environment::prepare($this->root);
|
|
self::assertSame($secret, Environment::ensureSecret($this->root));
|
|
self::assertSame('sqlite:///customer.db', $this->values()['DATABASE_URL']);
|
|
self::assertFileDoesNotExist($this->root.'/.env.local');
|
|
}
|
|
|
|
public function testLegacySettingsAreConsolidatedOnceWithBothOriginalsBackedUp(): void
|
|
{
|
|
Environment::prepare($this->root);
|
|
$legacy = "DATABASE_URL=sqlite:///live.db\nAPP_SECRET=existing-secret\nCUSTOM_OPTION=kept\n";
|
|
file_put_contents($this->root.'/.env.local', $legacy);
|
|
Environment::prepare($this->root);
|
|
self::assertSame('sqlite:///live.db', $this->values()['DATABASE_URL']);
|
|
self::assertSame('existing-secret', Environment::ensureSecret($this->root));
|
|
self::assertSame('kept', $this->values()['CUSTOM_OPTION']);
|
|
self::assertFileDoesNotExist($this->root.'/.env.local');
|
|
$backups = glob($this->root.'/var/backups/environment-*/.env.local');
|
|
self::assertCount(1, $backups);
|
|
self::assertSame($legacy, file_get_contents($backups[0]));
|
|
Environment::write($this->root, ['DATABASE_URL' => 'sqlite:///chosen.db']);
|
|
Environment::prepare($this->root);
|
|
self::assertSame('sqlite:///chosen.db', $this->values()['DATABASE_URL'], 'No legacy override after editing .env.');
|
|
self::assertCount(1, glob($this->root.'/var/backups/environment-*'));
|
|
}
|
|
|
|
public function testSecretsAndCustomSettingsRoundTripWithoutInterpolation(): void
|
|
{
|
|
Environment::prepare($this->root);
|
|
$value = 'a$NOT_AN_ENV_VALUE"b\\c';
|
|
Environment::write($this->root, ['APP_SECRET' => $value, 'CUSTOM_OPTION' => "one\ntwo"]);
|
|
Environment::write($this->root, ['SITE_ORIGIN' => 'https://example.org']);
|
|
self::assertSame($value, $this->values()['APP_SECRET']);
|
|
self::assertSame("one\ntwo", $this->values()['CUSTOM_OPTION']);
|
|
self::assertSame('null://null', $this->values()['MAILER_DSN']);
|
|
}
|
|
|
|
public function testFailedMigrationPreservesBothOriginalFiles(): void
|
|
{
|
|
Environment::prepare($this->root);
|
|
$before = file_get_contents($this->root.'/.env');
|
|
$legacy = "APP_SECRET=preserved\nDATABASE_URL=sqlite:///live.db\n";
|
|
file_put_contents($this->root.'/.env.local', $legacy);
|
|
unlink($this->root.'/.env.lock');
|
|
mkdir($this->root.'/.env.lock'); // An unwritable lock target, reproducible on Windows/Linux.
|
|
try {
|
|
Environment::prepare($this->root);
|
|
self::fail('Migration must report failure.');
|
|
} catch (\RuntimeException $error) {
|
|
self::assertStringContainsString('Schreibrechte', $error->getMessage());
|
|
}
|
|
self::assertSame($before, file_get_contents($this->root.'/.env'));
|
|
self::assertSame($legacy, file_get_contents($this->root.'/.env.local'));
|
|
}
|
|
|
|
public function testBootstrapIgnoresOtherOverlaysButHonorsExplicitServerVariables(): void
|
|
{
|
|
mkdir($this->root.'/config');
|
|
mkdir($this->root.'/vendor');
|
|
copy(dirname(__DIR__).'/config/bootstrap.php', $this->root.'/config/bootstrap.php');
|
|
file_put_contents($this->root.'/vendor/autoload.php', '<?php require '.var_export(dirname(__DIR__).'/vendor/autoload.php', true).';');
|
|
file_put_contents($this->root.'/.env', "APP_ENV=prod\nAPP_DEBUG=0\nAPP_SECRET=fixture-secret\nDATABASE_URL=sqlite:///chosen.db\n");
|
|
file_put_contents($this->root.'/.env.prod', "DATABASE_URL=sqlite:///wrong.db\n");
|
|
file_put_contents($this->root.'/.env.prod.local', "DATABASE_URL=sqlite:///also-wrong.db\n");
|
|
file_put_contents($this->root.'/.env.local.php', '<?php return ["DATABASE_URL" => "sqlite:///cached-wrong.db"];');
|
|
file_put_contents($this->root.'/boot.php', '<?php require __DIR__."/config/bootstrap.php"; echo $_SERVER["DATABASE_URL"];');
|
|
foreach ([null, 'sqlite:///server.db'] as $override) {
|
|
$environment = getenv();
|
|
unset($environment['DATABASE_URL']);
|
|
$environment['APP_ENV'] = 'prod';
|
|
if (null !== $override) {
|
|
$environment['DATABASE_URL'] = $override;
|
|
}
|
|
$process = proc_open([PHP_BINARY, $this->root.'/boot.php'], [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $this->root, $environment);
|
|
self::assertIsResource($process);
|
|
$output = stream_get_contents($pipes[1]);
|
|
$error = stream_get_contents($pipes[2]);
|
|
fclose($pipes[1]);
|
|
fclose($pipes[2]);
|
|
self::assertSame(0, proc_close($process), $error);
|
|
self::assertSame($override ?? 'sqlite:///chosen.db', $output);
|
|
self::assertFileDoesNotExist($this->root.'/.env.local');
|
|
}
|
|
}
|
|
|
|
private function values(): array
|
|
{
|
|
return (new Dotenv())->parse((string) file_get_contents($this->root.'/.env'));
|
|
}
|
|
}
|