| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Unit\Ip;
- use App\Domain\Ip\InvalidIpException;
- use App\Domain\Ip\IpAddress;
- use PHPUnit\Framework\Attributes\DataProvider;
- use PHPUnit\Framework\TestCase;
- final class IpAddressTest extends TestCase
- {
- public function testParsesSimpleIpv4(): void
- {
- $ip = IpAddress::fromString('203.0.113.42');
- $this->assertTrue($ip->isIpv4());
- $this->assertSame('203.0.113.42', $ip->text());
- $this->assertSame(16, strlen($ip->binary()));
- $this->assertSame(
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xcb\x00\x71\x2a",
- $ip->binary()
- );
- }
- public function testParsesIpv4Zero(): void
- {
- $ip = IpAddress::fromString('0.0.0.0');
- $this->assertTrue($ip->isIpv4());
- $this->assertSame('0.0.0.0', $ip->text());
- $this->assertSame(
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00",
- $ip->binary()
- );
- }
- public function testParsesIpv4Max(): void
- {
- $ip = IpAddress::fromString('255.255.255.255');
- $this->assertTrue($ip->isIpv4());
- $this->assertSame('255.255.255.255', $ip->text());
- }
- public function testParsesFullIpv6(): void
- {
- $ip = IpAddress::fromString('2001:0db8:0000:0000:0000:0000:0000:0001');
- $this->assertFalse($ip->isIpv4());
- $this->assertSame('2001:db8::1', $ip->text());
- $this->assertSame(16, strlen($ip->binary()));
- }
- public function testParsesZeroCompressedIpv6(): void
- {
- $ip = IpAddress::fromString('2001:db8::1');
- $this->assertFalse($ip->isIpv4());
- $this->assertSame('2001:db8::1', $ip->text());
- }
- public function testParsesIpv6Loopback(): void
- {
- $ip = IpAddress::fromString('::1');
- $this->assertFalse($ip->isIpv4());
- $this->assertSame('::1', $ip->text());
- }
- public function testParsesIpv6Unspecified(): void
- {
- $ip = IpAddress::fromString('::');
- $this->assertFalse($ip->isIpv4());
- $this->assertSame('::', $ip->text());
- }
- public function testParsesV4MappedIpv6KeepsIpv6Form(): void
- {
- $ip = IpAddress::fromString('::ffff:1.2.3.4');
- $this->assertFalse($ip->isIpv4(), 'v4-mapped passed in IPv6 form should report as IPv6');
- $this->assertSame(
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x01\x02\x03\x04",
- $ip->binary()
- );
- }
- public function testIpv4MapsIntoSameBinaryAsV4MappedIpv6(): void
- {
- $v4 = IpAddress::fromString('1.2.3.4');
- $v6 = IpAddress::fromString('::ffff:1.2.3.4');
- $this->assertSame($v4->binary(), $v6->binary());
- }
- public function testIpv6IsLowercased(): void
- {
- $ip = IpAddress::fromString('2001:DB8:ABCD::FFFF');
- $this->assertSame('2001:db8:abcd::ffff', $ip->text());
- }
- public function testIpv6FullyExpandedCanonicalizes(): void
- {
- $ip = IpAddress::fromString('fe80:0000:0000:0000:0000:0000:0000:0001');
- $this->assertSame('fe80::1', $ip->text());
- }
- public function testIpv6LinkLocalLowercased(): void
- {
- $ip = IpAddress::fromString('FE80::1');
- $this->assertSame('fe80::1', $ip->text());
- }
- public function testFromBinaryRoundtripIpv4(): void
- {
- $original = IpAddress::fromString('10.20.30.40');
- $copy = IpAddress::fromBinary($original->binary());
- $this->assertTrue($copy->isIpv4());
- $this->assertSame('10.20.30.40', $copy->text());
- }
- public function testFromBinaryRoundtripIpv6(): void
- {
- $original = IpAddress::fromString('2001:db8::dead:beef');
- $copy = IpAddress::fromBinary($original->binary());
- $this->assertFalse($copy->isIpv4());
- $this->assertSame('2001:db8::dead:beef', $copy->text());
- }
- public function testFromBinaryRejectsWrongLength(): void
- {
- $this->expectException(InvalidIpException::class);
- IpAddress::fromBinary("\x01\x02\x03");
- }
- public function testEqualsCompareByBinary(): void
- {
- $a = IpAddress::fromString('1.2.3.4');
- $b = IpAddress::fromString('::ffff:1.2.3.4');
- $c = IpAddress::fromString('1.2.3.5');
- $this->assertTrue($a->equals($b));
- $this->assertFalse($a->equals($c));
- }
- /**
- * @return iterable<string, array{0: string}>
- */
- public static function invalidProvider(): iterable
- {
- yield 'empty' => [''];
- yield 'whitespace only' => [' '];
- yield 'leading space' => [' 1.2.3.4'];
- yield 'trailing space' => ['1.2.3.4 '];
- yield 'inner whitespace' => ['1.2. 3.4'];
- yield 'integer-as-string' => ['1234567890'];
- yield 'three octets' => ['1.2.3'];
- yield 'five octets' => ['1.2.3.4.5'];
- yield 'octet > 255' => ['1.2.3.256'];
- yield 'negative octet' => ['1.2.3.-1'];
- yield 'leading zero v4' => ['010.0.0.1'];
- yield 'leading zero in last octet' => ['10.0.0.01'];
- yield 'empty octet' => ['10..0.1'];
- yield 'hex octet' => ['0x10.0.0.1'];
- yield 'alpha octet' => ['a.b.c.d'];
- yield 'just dots' => ['...'];
- yield 'incomplete v6' => ['2001:db8'];
- yield 'too many groups v6' => ['1:2:3:4:5:6:7:8:9'];
- yield 'invalid hex group v6' => ['ggggg::1'];
- yield 'double :: v6' => ['1::2::3'];
- yield 'bracketed v6' => ['[::1]'];
- yield 'zone id v6' => ['fe80::1%eth0'];
- yield 'random garbage' => ['hello world'];
- yield 'sql injection-ish' => ["1.2.3.4'; DROP TABLE--"];
- yield 'newline embedded' => ["1.2.3.4\n"];
- yield 'tab embedded' => ["1.2.3.4\t"];
- yield 'just slash' => ['/'];
- yield 'with prefix' => ['1.2.3.4/24'];
- yield 'just colon' => [':'];
- yield 'just dot' => ['.'];
- yield 'numeric overflow octet' => ['1.2.3.99999'];
- }
- #[DataProvider('invalidProvider')]
- public function testRejectsInvalidInput(string $input): void
- {
- $this->expectException(InvalidIpException::class);
- IpAddress::fromString($input);
- }
- }
|