AuditRepositoryTest.php 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Repositories;
  4. use App\Repositories\AuditRepository;
  5. use App\Services\AuditLogger;
  6. use App\Tests\TestCase;
  7. use InvalidArgumentException;
  8. /**
  9. * R01-N11: AuditRepository::distinctColumn interpolates its argument into
  10. * SQL. The method is private and both internal callers pass literals, but
  11. * the runtime whitelist guard makes the contract explicit so a future
  12. * refactor can't open an injection vector.
  13. */
  14. final class AuditRepositoryTest extends TestCase
  15. {
  16. public function testDistinctActionsReturnsSortedUniqueValues(): void
  17. {
  18. $pdo = $this->makeDb();
  19. $logger = new AuditLogger($pdo);
  20. $logger->record('UPDATE', 'worker', 1, ['n' => 1], ['n' => 2]);
  21. $logger->record('CREATE', 'worker', 2, null, ['n' => 1]);
  22. $logger->record('UPDATE', 'sprint', 3, ['n' => 1], ['n' => 2]);
  23. $repo = new AuditRepository($pdo);
  24. $this->assertSame(['CREATE', 'UPDATE'], $repo->distinctActions());
  25. }
  26. public function testDistinctEntityTypesReturnsSortedUniqueValues(): void
  27. {
  28. $pdo = $this->makeDb();
  29. $logger = new AuditLogger($pdo);
  30. $logger->record('UPDATE', 'worker', 1, ['n' => 1], ['n' => 2]);
  31. $logger->record('UPDATE', 'sprint', 2, ['n' => 1], ['n' => 2]);
  32. $logger->record('UPDATE', 'worker', 3, ['n' => 1], ['n' => 2]);
  33. $repo = new AuditRepository($pdo);
  34. $this->assertSame(['sprint', 'worker'], $repo->distinctEntityTypes());
  35. }
  36. public function testDistinctColumnRejectsUnknownColumnViaReflection(): void
  37. {
  38. $pdo = $this->makeDb();
  39. $repo = new AuditRepository($pdo);
  40. $rm = new \ReflectionMethod($repo, 'distinctColumn');
  41. $rm->setAccessible(true);
  42. $this->expectException(InvalidArgumentException::class);
  43. $rm->invoke($repo, 'user_email');
  44. }
  45. public function testDistinctColumnRejectsInjectionAttemptViaReflection(): void
  46. {
  47. $pdo = $this->makeDb();
  48. $repo = new AuditRepository($pdo);
  49. $rm = new \ReflectionMethod($repo, 'distinctColumn');
  50. $rm->setAccessible(true);
  51. $this->expectException(InvalidArgumentException::class);
  52. $rm->invoke($repo, 'action; DROP TABLE audit_log; --');
  53. }
  54. }