| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Unit\Ip;
- use App\Domain\Ip\Cidr;
- use App\Domain\Ip\InvalidCidrException;
- use App\Domain\Ip\IpAddress;
- use PHPUnit\Framework\TestCase;
- final class CidrTest extends TestCase
- {
- public function testParsesSimpleIpv4Cidr(): void
- {
- $cidr = Cidr::fromString('10.0.0.0/8');
- $this->assertTrue($cidr->isIpv4());
- $this->assertSame(8, $cidr->originalPrefix());
- $this->assertSame(104, $cidr->prefixLength());
- $this->assertSame('10.0.0.0/8', $cidr->text());
- }
- public function testParsesSlash32SingleHost(): void
- {
- $cidr = Cidr::fromString('192.168.1.42/32');
- $this->assertTrue($cidr->contains(IpAddress::fromString('192.168.1.42')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('192.168.1.43')));
- }
- public function testV4Slash24Containment(): void
- {
- $cidr = Cidr::fromString('10.0.0.0/24');
- $this->assertTrue($cidr->contains(IpAddress::fromString('10.0.0.0')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('10.0.0.1')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('10.0.0.255')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('10.0.1.0')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('11.0.0.0')));
- }
- public function testV4Slash16Containment(): void
- {
- $cidr = Cidr::fromString('172.16.0.0/16');
- $this->assertTrue($cidr->contains(IpAddress::fromString('172.16.0.0')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('172.16.255.255')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('172.17.0.0')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('172.15.255.255')));
- }
- public function testV4Slash0MatchesAllV4(): void
- {
- $cidr = Cidr::fromString('0.0.0.0/0');
- $this->assertSame(96, $cidr->prefixLength());
- $this->assertTrue($cidr->contains(IpAddress::fromString('0.0.0.0')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('255.255.255.255')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('1.2.3.4')));
- // /0 of v4 = ::ffff:0:0/96 → does NOT match pure IPv6
- $this->assertFalse($cidr->contains(IpAddress::fromString('2001:db8::1')));
- }
- public function testV6Slash0MatchesEverything(): void
- {
- $cidr = Cidr::fromString('::/0');
- $this->assertSame(0, $cidr->prefixLength());
- $this->assertTrue($cidr->contains(IpAddress::fromString('::1')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('2001:db8::1')));
- // Even v4 (which is stored as v4-mapped v6) matches /0
- $this->assertTrue($cidr->contains(IpAddress::fromString('1.2.3.4')));
- }
- public function testV6Slash128SingleHost(): void
- {
- $cidr = Cidr::fromString('2001:db8::1/128');
- $this->assertTrue($cidr->contains(IpAddress::fromString('2001:db8::1')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('2001:db8::2')));
- }
- public function testV6Slash64Containment(): void
- {
- $cidr = Cidr::fromString('2001:db8::/64');
- $this->assertTrue($cidr->contains(IpAddress::fromString('2001:db8::1')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('2001:db8::ffff:ffff:ffff:ffff')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('2001:db9::1')));
- }
- public function testV6CidrDoesNotContainV4(): void
- {
- $cidr = Cidr::fromString('2001:db8::/32');
- $this->assertFalse($cidr->contains(IpAddress::fromString('1.2.3.4')));
- }
- public function testV4InV4MappedV6Cidr(): void
- {
- // ::ffff:1.0.0.0/120 covers v4-mapped 1.0.0.0/24
- $cidr = Cidr::fromString('::ffff:1.0.0.0/120');
- $this->assertFalse($cidr->isIpv4(), 'CIDR is in v6 syntax even though it covers v4');
- $this->assertSame(120, $cidr->prefixLength());
- $this->assertTrue($cidr->contains(IpAddress::fromString('1.0.0.5')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('1.0.0.255')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('1.0.1.0')));
- }
- public function testV4MappedV6AddressInsideV4Cidr(): void
- {
- $cidr = Cidr::fromString('1.0.0.0/24');
- // The v4-mapped IPv6 form must still be contained because internal storage is unified.
- $this->assertTrue($cidr->contains(IpAddress::fromString('::ffff:1.0.0.7')));
- }
- public function testNetworkAddressMaskedFromHostBits(): void
- {
- $cidr = Cidr::fromString('10.1.2.99/24');
- $expected = IpAddress::fromString('10.1.2.0');
- $this->assertSame($expected->binary(), $cidr->network());
- $this->assertSame('10.1.2.0/24', $cidr->text());
- }
- public function testV6NetworkAddressMaskedFromHostBits(): void
- {
- $cidr = Cidr::fromString('2001:db8:abcd:1234::ff/56');
- $this->assertSame('2001:db8:abcd:1200::/56', $cidr->text());
- }
- public function testRejectsMissingSlash(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString('10.0.0.0');
- }
- public function testRejectsEmpty(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString('');
- }
- public function testRejectsBadIp(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString('garbage/8');
- }
- public function testRejectsNegativePrefix(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString('10.0.0.0/-1');
- }
- public function testRejectsV4PrefixOver32(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString('10.0.0.0/33');
- }
- public function testRejectsV6PrefixOver128(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString('2001:db8::/129');
- }
- public function testRejectsNonNumericPrefix(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString('10.0.0.0/abc');
- }
- public function testRejectsMultipleSlashes(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString('10.0.0.0/24/8');
- }
- public function testRejectsLeadingWhitespace(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString(' 10.0.0.0/24');
- }
- public function testRejectsEmptyPrefix(): void
- {
- $this->expectException(InvalidCidrException::class);
- Cidr::fromString('10.0.0.0/');
- }
- public function testNonByteAlignedV4Prefix(): void
- {
- // /20 fixes the first 20 bits = 8 (byte 0) + 8 (byte 1) + 4 (high
- // nibble of byte 2). Exercises the partial-byte mask path.
- // 10.16.0.0/20 covers 10.16.0.0 — 10.16.15.255 (4096 addresses).
- $cidr = Cidr::fromString('10.16.0.0/20');
- $this->assertTrue($cidr->contains(IpAddress::fromString('10.16.0.0')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('10.16.15.255')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('10.16.5.42')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('10.16.16.0')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('10.15.255.255')));
- $this->assertSame('10.16.0.0/20', $cidr->text());
- }
- public function testNonByteAlignedV6Prefix(): void
- {
- // /17 fixes 16 bits + 1 high bit of byte 2. 2001:8000::/17 covers
- // any address whose first 17 bits start with 0x2001 followed by a 1.
- $cidr = Cidr::fromString('2001:8000::/17');
- $this->assertTrue($cidr->contains(IpAddress::fromString('2001:8000::')));
- $this->assertTrue($cidr->contains(IpAddress::fromString('2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('2001:7fff:ffff::')));
- $this->assertFalse($cidr->contains(IpAddress::fromString('2002::')));
- }
- public function testNonByteAlignedHostMaskingClearsBits(): void
- {
- // 10.16.5.7/20 should mask the host bits to 10.16.0.0/20
- $cidr = Cidr::fromString('10.16.5.7/20');
- $this->assertSame('10.16.0.0/20', $cidr->text());
- }
- }
|