Files
computer-playbook/tests/unit/php/roles/web-app-joomla/files/ToggleDebugTest.php
kevinveenbirkenbachandClaude Opus 5 7a9c085c5a test(unit): first php and ruby suites
composer.json, phpunit.xml and the ruby runner were already in place and
had never run a single test, so 24 php and 20 ruby files shipped with
nothing but php -l and ruby -c behind them.

The two joomla scripts are pure file-in/file-out driven by env, which is
testable without Joomla: the tests drive the real script against a
temporary configuration.php. They pin what the code silently does today —
apostrophe escaping in the error level and in the db password, the two
repair regexes for malformed public lines, a missing key that is left
absent rather than added, and the asymmetry that toggle_debug exits 1 on a
missing file while configure_db exits 0.

The mastodon initializer is an env gate that downgrades URL building to
http. The test pins the security-relevant half: with OIDC off, with an
https issuer and with no issuer at all, SWD stays undefined. The http case
proves the branch is taken.

The three nocheck markers had to go with it: the mirrored-unit-test lint
fails a file that carries both a test and an exemption. package_plugin.php
keeps its marker, it is a build step.

vendor/ and .phpunit.result.cache were not ignored; composer install drops
25 packages into the tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 19:17:53 +02:00

137 lines
4.4 KiB
PHP

<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class ToggleDebugTest extends TestCase
{
/**
* Run the script against a throwaway configuration.php.
*
* @param array<string, string> $env extra environment for the child
* @param string|null $contents fixture content, null to omit the file
* @return array{0: string, 1: string, 2: int, 3: string|null}
* stdout, stderr, exit code and the file content afterwards
*/
private function runScript(array $env, ?string $contents): array
{
$file = tempnam(sys_get_temp_dir(), 'jcfg');
if ($contents === null) {
unlink($file);
} else {
file_put_contents($file, $contents);
}
$script = dirname(__DIR__, 6) . '/roles/web-app-joomla/files/php/toggle_debug.php';
$process = proc_open(
[PHP_BINARY, $script],
[1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
$pipes,
null,
$env + ['J_CONFIG_FILE' => $file, 'PATH' => (string) getenv('PATH')]
);
$stdout = (string) stream_get_contents($pipes[1]);
$stderr = (string) stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$code = proc_close($process);
$after = is_file($file) ? (string) file_get_contents($file) : null;
if (is_file($file)) {
unlink($file);
}
return [$stdout, $stderr, $code, $after];
}
private const CONFIG = <<<'PHP'
<?php
class JConfig {
public $debug = true;
public $error_reporting = "none";
public $sitename = 'Infinito';
}
PHP;
public function testExistingDebugFlagIsFlippedOff(): void
{
[$stdout, , $code, $after] = $this->runScript(
['J_MODE_DEBUG' => '0', 'J_ERR_LEVEL' => 'default'],
self::CONFIG
);
$this->assertSame(0, $code);
$this->assertSame('changed', $stdout);
$this->assertStringContainsString('public $debug = false;', (string) $after);
$this->assertStringNotContainsString('public $debug = true;', (string) $after);
}
public function testDebugFlagIsAppendedWhenAbsent(): void
{
[, , , $after] = $this->runScript(
['J_MODE_DEBUG' => '1', 'J_ERR_LEVEL' => 'default'],
"<?php\nclass JConfig {\n\tpublic \$sitename = 'Infinito';\n}\n"
);
$this->assertStringContainsString('public $debug = true;', (string) $after);
$this->assertStringContainsString("public \$error_reporting = 'default';", (string) $after);
}
public function testUnrelatedSettingsSurvive(): void
{
[, , , $after] = $this->runScript(
['J_MODE_DEBUG' => '0', 'J_ERR_LEVEL' => 'maximum'],
self::CONFIG
);
$this->assertStringContainsString("public \$sitename = 'Infinito';", (string) $after);
}
public function testErrorLevelWithApostropheIsEscaped(): void
{
[, , , $after] = $this->runScript(
['J_MODE_DEBUG' => '0', 'J_ERR_LEVEL' => "it's"],
self::CONFIG
);
$this->assertStringContainsString("public \$error_reporting = 'it\\'s';", (string) $after);
}
public function testMalformedPublicLinesAreDropped(): void
{
[, , , $after] = $this->runScript(
['J_MODE_DEBUG' => '0', 'J_ERR_LEVEL' => 'default'],
"<?php\nclass JConfig {\n\tpublic 1 = broken;\n\tpublic = maximum;\n\tpublic \$debug = true;\n}\n"
);
$this->assertStringNotContainsString('public 1 =', (string) $after);
$this->assertStringNotContainsString('public = maximum;', (string) $after);
}
public function testMissingConfigFileFailsLoudly(): void
{
[, $stderr, $code] = $this->runScript(
['J_MODE_DEBUG' => '0', 'J_ERR_LEVEL' => 'default'],
null
);
$this->assertSame(1, $code);
$this->assertStringContainsString('configuration.php missing', $stderr);
}
public function testRewriteIsStableOnASecondRun(): void
{
[, , , $once] = $this->runScript(
['J_MODE_DEBUG' => '0', 'J_ERR_LEVEL' => 'maximum'],
self::CONFIG
);
[, , , $twice] = $this->runScript(
['J_MODE_DEBUG' => '0', 'J_ERR_LEVEL' => 'maximum'],
(string) $once
);
$this->assertSame($once, $twice);
}
}