1
0

AuditControllerTest.php 4.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Controllers;
  4. use App\Controllers\AuditController;
  5. use PHPUnit\Framework\Attributes\DataProvider;
  6. use PHPUnit\Framework\TestCase;
  7. /**
  8. * R01-N12: pin AuditController::validateDateFilters. The repo concatenates
  9. * "T00:00:00Z" / "T23:59:59Z" onto whatever string it gets and binds the
  10. * result against the ISO-8601 `occurred_at` column — garbage in silently
  11. * hides rows. The controller is the gate; this test pins which inputs
  12. * pass through and which get dropped + flagged.
  13. */
  14. final class AuditControllerTest extends TestCase
  15. {
  16. /**
  17. * @return list<array{string,string,string,string,list<string>,string}>
  18. */
  19. public static function dateCases(): array
  20. {
  21. // [from, to, expectedFrom, expectedTo, expectedErrorKeys, label]
  22. return [
  23. ['', '', '', '', [], 'no filters → no errors'],
  24. ['2026-01-31', '', '2026-01-31', '', [], 'from-only valid'],
  25. ['', '2026-12-31', '', '2026-12-31', [], 'to-only valid'],
  26. ['2026-01-01', '2026-12-31', '2026-01-01', '2026-12-31', [], 'both valid'],
  27. ['2024/01/01', '', '', '', ['from_date'], 'slashes → from dropped, error reported'],
  28. ['', '2024/01/01', '', '', ['to_date'], 'slashes → to dropped, error reported'],
  29. ['2024/01/01', '2024/12/31', '', '', ['from_date', 'to_date'], 'both garbage → both dropped, both errors'],
  30. ['2026-01-31', 'tomorrow', '2026-01-31', '', ['to_date'], 'one-side invalid → only that side errors, the other passes'],
  31. ['2026-13-01', '', '', '', ['from_date'], 'impossible month → rejected'],
  32. ['2026-02-30', '', '', '', ['from_date'], 'impossible day for month → rejected (strict round-trip)'],
  33. ['2026-1-1', '', '', '', ['from_date'], 'unpadded → rejected (strict round-trip)'],
  34. [' 2026-01-01', '', '', '', ['from_date'], 'leading space → rejected'],
  35. ['2026-01-01 ', '', '', '', ['from_date'], 'trailing space → rejected'],
  36. ["'; DROP TABLE audit_log; --", '', '', '', ['from_date'], 'injection-shaped string → rejected'],
  37. ];
  38. }
  39. /** @param list<string> $expectedErrorKeys */
  40. #[DataProvider('dateCases')]
  41. public function testValidateDateFilters(
  42. string $from,
  43. string $to,
  44. string $expectedFrom,
  45. string $expectedTo,
  46. array $expectedErrorKeys,
  47. string $label,
  48. ): void {
  49. $out = AuditController::validateDateFilters($from, $to);
  50. $this->assertSame($expectedFrom, $out['from'], "{$label}: from");
  51. $this->assertSame($expectedTo, $out['to'], "{$label}: to");
  52. $this->assertSame(
  53. $expectedErrorKeys,
  54. array_keys($out['errors']),
  55. "{$label}: error keys",
  56. );
  57. // Error messages are non-empty when present (keeps the contract for
  58. // the view, which renders {{ dateErrors.from_date }} verbatim).
  59. foreach ($out['errors'] as $key => $msg) {
  60. $this->assertNotSame('', $msg, "{$label}: error message for {$key} is non-empty");
  61. }
  62. }
  63. public function testValidatorPreservesValidValuesVerbatim(): void
  64. {
  65. // Boundary that the repo concatenates onto: a leap day. Confirms
  66. // we don't normalise or reformat — the repo gets exactly what the
  67. // user typed (after gating).
  68. $out = AuditController::validateDateFilters('2024-02-29', '2024-02-29');
  69. $this->assertSame('2024-02-29', $out['from']);
  70. $this->assertSame('2024-02-29', $out['to']);
  71. $this->assertSame([], $out['errors']);
  72. }
  73. public function testValidatorRejectsNonLeapFebTwentyNine(): void
  74. {
  75. // 2025 is not a leap year. createFromFormat is lenient and silently
  76. // rolls over to 2025-03-01; the round-trip equality check we use
  77. // is what catches it.
  78. $out = AuditController::validateDateFilters('2025-02-29', '');
  79. $this->assertSame('', $out['from']);
  80. $this->assertArrayHasKey('from_date', $out['errors']);
  81. }
  82. }