EffectiveStatusServiceTest.php 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Unit\Reputation;
  4. use App\Domain\Ip\Cidr;
  5. use App\Domain\Ip\IpAddress;
  6. use App\Domain\Reputation\CidrEvaluator;
  7. use App\Domain\Reputation\EffectiveStatus;
  8. use App\Domain\Reputation\EffectiveStatusService;
  9. use App\Infrastructure\Reputation\CidrEvaluatorFactory;
  10. use PHPUnit\Framework\TestCase;
  11. /**
  12. * Locks the SPEC §5 precedence: allowlist > manual block > scored > clean.
  13. * Score-vs-policy lands in M07; until then, anything not on a list is
  14. * `Clean`.
  15. */
  16. final class EffectiveStatusServiceTest extends TestCase
  17. {
  18. public function testAllowlistWinsOverManualBlock(): void
  19. {
  20. $bin = IpAddress::fromString('198.51.100.5')->binary();
  21. $factory = $this->factoryReturning(new CidrEvaluator(
  22. manualIpBins: [$bin],
  23. manualSubnets: [],
  24. allowlistIpBins: [$bin],
  25. allowlistSubnets: [],
  26. ));
  27. $service = new EffectiveStatusService($factory);
  28. self::assertSame(
  29. EffectiveStatus::Allowlisted,
  30. $service->forIp(IpAddress::fromString('198.51.100.5'))
  31. );
  32. }
  33. public function testManualBlockReturnedWhenNotAllowlisted(): void
  34. {
  35. $bin = IpAddress::fromString('203.0.113.42')->binary();
  36. $factory = $this->factoryReturning(new CidrEvaluator([$bin], [], [], []));
  37. $service = new EffectiveStatusService($factory);
  38. self::assertSame(
  39. EffectiveStatus::ManuallyBlocked,
  40. $service->forIp(IpAddress::fromString('203.0.113.42'))
  41. );
  42. }
  43. public function testIpInsideAllowlistedSubnetEvenWithSeparateManualBlockIsAllowlisted(): void
  44. {
  45. $allow = Cidr::fromString('203.0.113.0/24');
  46. $factory = $this->factoryReturning(new CidrEvaluator(
  47. manualIpBins: [IpAddress::fromString('203.0.113.42')->binary()],
  48. manualSubnets: [],
  49. allowlistIpBins: [],
  50. allowlistSubnets: [$allow],
  51. ));
  52. $service = new EffectiveStatusService($factory);
  53. self::assertSame(
  54. EffectiveStatus::Allowlisted,
  55. $service->forIp(IpAddress::fromString('203.0.113.42'))
  56. );
  57. }
  58. public function testCleanWhenNothingMatches(): void
  59. {
  60. $factory = $this->factoryReturning(new CidrEvaluator([], [], [], []));
  61. $service = new EffectiveStatusService($factory);
  62. self::assertSame(
  63. EffectiveStatus::Clean,
  64. $service->forIp(IpAddress::fromString('203.0.113.99'))
  65. );
  66. }
  67. private function factoryReturning(CidrEvaluator $evaluator): CidrEvaluatorFactory
  68. {
  69. return new class ($evaluator) extends CidrEvaluatorFactory {
  70. public function __construct(private readonly CidrEvaluator $fixed)
  71. {
  72. // Deliberately not calling parent::__construct — this stub
  73. // never queries the DB.
  74. }
  75. public function get(): CidrEvaluator
  76. {
  77. return $this->fixed;
  78. }
  79. public function invalidate(): void
  80. {
  81. // no-op
  82. }
  83. };
  84. }
  85. }