1
0

MmdbEnrichmentServiceTest.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Unit\Enrichment;
  4. use App\Domain\Ip\IpAddress;
  5. use App\Domain\Time\Clock;
  6. use App\Domain\Time\SystemClock;
  7. use App\Infrastructure\Enrichment\MaxMindRecordAdapter;
  8. use App\Infrastructure\Enrichment\MmdbEnrichmentService;
  9. use Monolog\Handler\TestHandler;
  10. use Monolog\Logger;
  11. use PHPUnit\Framework\TestCase;
  12. /**
  13. * Drives MmdbEnrichmentService against the vendored MaxMind test
  14. * fixtures (Apache-2.0). Covers the load → lookup → result → reload
  15. * lifecycle plus the warn-once-on-missing-DB invariant.
  16. */
  17. final class MmdbEnrichmentServiceTest extends TestCase
  18. {
  19. private const COUNTRY_DB = __DIR__ . '/../../Fixtures/geoip/country.mmdb';
  20. private const ASN_DB = __DIR__ . '/../../Fixtures/geoip/asn.mmdb';
  21. public function testLooksUpKnownIpv4Fixture(): void
  22. {
  23. $service = $this->makeService(self::COUNTRY_DB, self::ASN_DB);
  24. $result = $service->enrich(IpAddress::fromString('81.2.69.142'));
  25. self::assertSame('GB', $result->countryCode);
  26. // The fixture's ASN db doesn't carry every test IP — country alone is the load-bearing assertion.
  27. }
  28. public function testIpv6Lookup(): void
  29. {
  30. $service = $this->makeService(self::COUNTRY_DB, self::ASN_DB);
  31. // The MaxMind fixture covers v4-mapped v6 of 81.2.69.142.
  32. $result = $service->enrich(IpAddress::fromString('::ffff:81.2.69.142'));
  33. self::assertSame('GB', $result->countryCode);
  34. }
  35. public function testUnknownIpReturnsEmpty(): void
  36. {
  37. $service = $this->makeService(self::COUNTRY_DB, self::ASN_DB);
  38. // A reserved-but-not-in-fixture IP.
  39. $result = $service->enrich(IpAddress::fromString('192.0.2.99'));
  40. self::assertNull($result->countryCode);
  41. self::assertNull($result->asn);
  42. self::assertTrue($result->isEmpty());
  43. }
  44. public function testMissingDbWarnsOnceAndReturnsEmpty(): void
  45. {
  46. $logger = new Logger('t');
  47. $handler = new TestHandler();
  48. $logger->pushHandler($handler);
  49. $service = new MmdbEnrichmentService(
  50. countryDbPath: '/nonexistent/country.mmdb',
  51. asnDbPath: '/nonexistent/asn.mmdb',
  52. adapter: new MaxMindRecordAdapter(),
  53. clock: new SystemClock(),
  54. logger: $logger,
  55. );
  56. // Two lookups should still produce only one warning.
  57. $a = $service->enrich(IpAddress::fromString('1.2.3.4'));
  58. $b = $service->enrich(IpAddress::fromString('5.6.7.8'));
  59. self::assertTrue($a->isEmpty());
  60. self::assertTrue($b->isEmpty());
  61. self::assertFalse($service->isReady());
  62. $messages = array_map(static fn ($r): string => $r->message, $handler->getRecords());
  63. $matches = array_filter($messages, static fn (string $m): bool => $m === 'geoip_db_unavailable');
  64. self::assertCount(1, $matches);
  65. }
  66. public function testReloadReadersClearsState(): void
  67. {
  68. $service = $this->makeService(self::COUNTRY_DB, self::ASN_DB);
  69. $service->enrich(IpAddress::fromString('81.2.69.142'));
  70. $service->reloadReaders();
  71. // Should still work after reload.
  72. $result = $service->enrich(IpAddress::fromString('81.2.69.142'));
  73. self::assertSame('GB', $result->countryCode);
  74. }
  75. private function makeService(string $country, string $asn, ?Clock $clock = null): MmdbEnrichmentService
  76. {
  77. $logger = new Logger('t');
  78. $logger->pushHandler(new TestHandler());
  79. return new MmdbEnrichmentService(
  80. countryDbPath: $country,
  81. asnDbPath: $asn,
  82. adapter: new MaxMindRecordAdapter(),
  83. clock: $clock ?? new SystemClock(),
  84. logger: $logger,
  85. );
  86. }
  87. }