1
0

AuditControllerTest.php 4.9 KB

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