AuditRepositoryTest.php 2.6 KB

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