ConfigTest.php 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Unit\App;
  4. use App\App\Config;
  5. use PHPUnit\Framework\TestCase;
  6. /**
  7. * SEC_REVIEW F35 — `INTERNAL_JOB_TOKEN` startup validation.
  8. */
  9. final class ConfigTest extends TestCase
  10. {
  11. public function testEmptyTokenIsRejected(): void
  12. {
  13. $errors = Config::collectErrors(['internal_job_token' => '']);
  14. self::assertNotEmpty($errors);
  15. self::assertStringContainsString('INTERNAL_JOB_TOKEN', $errors[0]);
  16. }
  17. public function testMissingKeyIsRejected(): void
  18. {
  19. $errors = Config::collectErrors([]);
  20. self::assertNotEmpty($errors);
  21. self::assertStringContainsString('INTERNAL_JOB_TOKEN', $errors[0]);
  22. }
  23. public function testShortHexIsRejected(): void
  24. {
  25. $errors = Config::collectErrors(['internal_job_token' => str_repeat('a', 31)]);
  26. self::assertNotEmpty($errors);
  27. }
  28. public function testNonHexIsRejected(): void
  29. {
  30. // 32+ chars but contains non-hex — caught by the charset rule.
  31. $errors = Config::collectErrors([
  32. 'internal_job_token' => 'this-token-is-32-plus-chars-but-not-hex-at-all',
  33. ]);
  34. self::assertNotEmpty($errors);
  35. }
  36. public function testWeakLiteralFooIsRejected(): void
  37. {
  38. // The exact case the SEC_REVIEW called out.
  39. $errors = Config::collectErrors(['internal_job_token' => 'foo']);
  40. self::assertNotEmpty($errors);
  41. }
  42. public function testThirtyTwoHexCharsAccepted(): void
  43. {
  44. $errors = Config::collectErrors([
  45. 'internal_job_token' => str_repeat('a', 32),
  46. ]);
  47. self::assertSame([], $errors);
  48. }
  49. public function testSixtyFourHexCharsAcceptedDocumentedForm(): void
  50. {
  51. // openssl rand -hex 32 → 64 hex chars; the documented form.
  52. $errors = Config::collectErrors([
  53. 'internal_job_token' => bin2hex(random_bytes(32)),
  54. ]);
  55. self::assertSame([], $errors);
  56. }
  57. public function testUppercaseHexAccepted(): void
  58. {
  59. $errors = Config::collectErrors([
  60. 'internal_job_token' => str_repeat('A', 32),
  61. ]);
  62. self::assertSame([], $errors);
  63. }
  64. public function testValidateOrExitWritesToStderrAndExits(): void
  65. {
  66. // Run the failing branch in a subprocess so we can observe both
  67. // the non-zero exit and the STDERR text without taking down PHPUnit.
  68. $script = <<<'PHP'
  69. <?php
  70. declare(strict_types=1);
  71. require %s;
  72. App\App\Config::validateOrExit(['internal_job_token' => 'too-short']);
  73. PHP;
  74. $autoload = var_export(__DIR__ . '/../../../vendor/autoload.php', true);
  75. $script = sprintf($script, $autoload);
  76. $tmp = tempnam(sys_get_temp_dir(), 'cfg');
  77. self::assertIsString($tmp);
  78. file_put_contents($tmp, $script);
  79. $stderr = '';
  80. $process = proc_open(
  81. [PHP_BINARY, $tmp],
  82. [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
  83. $pipes,
  84. );
  85. self::assertIsResource($process);
  86. fclose($pipes[1]);
  87. $stderr = (string) stream_get_contents($pipes[2]);
  88. fclose($pipes[2]);
  89. $exitCode = proc_close($process);
  90. @unlink($tmp);
  91. self::assertSame(1, $exitCode);
  92. self::assertStringContainsString('INTERNAL_JOB_TOKEN', $stderr);
  93. }
  94. }