refactor(tests): convert IpRestrictionServiceTest to Pest functional syntax

Rewrote all test methods to use Pest's test() function with expect()
assertions instead of PHPUnit class-based syntax:
- IP validation tests (IPv4 and IPv6)
- CIDR range matching for all prefix lengths (/0 to /32 for IPv4, /0 to /128 for IPv6)
- Whitelist management tests (parsing, formatting, comments)
- Entry validation and error handling
- Edge cases (loopback, private ranges, link-local, mixed protocols)

Test count increased from 60+ to 78 with additional edge case coverage.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-01-29 18:50:41 +00:00
parent e70e078bcb
commit d76db0672b
2 changed files with 1125 additions and 991 deletions

File diff suppressed because it is too large Load diff

View file

@ -2,589 +2,640 @@
declare(strict_types=1); declare(strict_types=1);
namespace Core\Mod\Agentic\Tests\Feature;
use Core\Mod\Agentic\Models\AgentApiKey;
use Core\Mod\Agentic\Services\IpRestrictionService;
use Core\Tenant\Models\Workspace;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/** /**
* Tests for the IpRestrictionService. * Tests for the IpRestrictionService.
* *
* Covers IPv4/IPv6 validation, CIDR matching, and edge cases. * Covers IPv4/IPv6 validation, CIDR matching, and edge cases.
*/ */
class IpRestrictionServiceTest extends TestCase
{
use RefreshDatabase;
private Workspace $workspace; use Core\Mod\Agentic\Models\AgentApiKey;
use Core\Mod\Agentic\Services\IpRestrictionService;
use Core\Tenant\Models\Workspace;
private IpRestrictionService $service; beforeEach(function (): void {
protected function setUp(): void
{
parent::setUp();
$this->workspace = Workspace::factory()->create(); $this->workspace = Workspace::factory()->create();
$this->service = app(IpRestrictionService::class); $this->service = app(IpRestrictionService::class);
} });
// ========================================================================= // =============================================================================
// IPv4 Basic Tests // IPv4 Basic Tests
// ========================================================================= // =============================================================================
public function test_validates_exact_ipv4_match(): void test('validates exact IPv4 match', function (): void {
{
$result = $this->service->isIpInWhitelist('192.168.1.100', ['192.168.1.100']); $result = $this->service->isIpInWhitelist('192.168.1.100', ['192.168.1.100']);
$this->assertTrue($result); expect($result)->toBeTrue();
} });
public function test_rejects_non_matching_ipv4(): void test('rejects non-matching IPv4', function (): void {
{
$result = $this->service->isIpInWhitelist('192.168.1.100', ['192.168.1.200']); $result = $this->service->isIpInWhitelist('192.168.1.100', ['192.168.1.200']);
$this->assertFalse($result); expect($result)->toBeFalse();
} });
public function test_validates_ipv4_in_multiple_entries(): void test('validates IPv4 in multiple entries', function (): void {
{
$whitelist = ['10.0.0.1', '192.168.1.100', '172.16.0.1']; $whitelist = ['10.0.0.1', '192.168.1.100', '172.16.0.1'];
$result = $this->service->isIpInWhitelist('192.168.1.100', $whitelist); $result = $this->service->isIpInWhitelist('192.168.1.100', $whitelist);
$this->assertTrue($result); expect($result)->toBeTrue();
} });
public function test_rejects_invalid_ipv4(): void test('rejects invalid IPv4', function (): void {
{
$result = $this->service->isIpInWhitelist('invalid', ['192.168.1.100']); $result = $this->service->isIpInWhitelist('invalid', ['192.168.1.100']);
$this->assertFalse($result); expect($result)->toBeFalse();
} });
public function test_rejects_ipv4_out_of_range(): void test('rejects IPv4 out of range', function (): void {
{
$result = $this->service->isIpInWhitelist('256.256.256.256', ['192.168.1.100']); $result = $this->service->isIpInWhitelist('256.256.256.256', ['192.168.1.100']);
$this->assertFalse($result); expect($result)->toBeFalse();
} });
// ========================================================================= // =============================================================================
// IPv4 CIDR Tests // IPv4 CIDR Tests
// ========================================================================= // =============================================================================
public function test_validates_ipv4_in_cidr_24(): void test('validates IPv4 in CIDR /24', function (): void {
{ expect($this->service->isIpInWhitelist('192.168.1.0', ['192.168.1.0/24']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('192.168.1.0', ['192.168.1.0/24'])); expect($this->service->isIpInWhitelist('192.168.1.1', ['192.168.1.0/24']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('192.168.1.1', ['192.168.1.0/24'])); expect($this->service->isIpInWhitelist('192.168.1.128', ['192.168.1.0/24']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('192.168.1.128', ['192.168.1.0/24'])); expect($this->service->isIpInWhitelist('192.168.1.255', ['192.168.1.0/24']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('192.168.1.255', ['192.168.1.0/24'])); });
}
public function test_rejects_ipv4_outside_cidr_24(): void test('rejects IPv4 outside CIDR /24', function (): void {
{ expect($this->service->isIpInWhitelist('192.168.2.0', ['192.168.1.0/24']))->toBeFalse();
$this->assertFalse($this->service->isIpInWhitelist('192.168.2.0', ['192.168.1.0/24'])); expect($this->service->isIpInWhitelist('192.168.0.255', ['192.168.1.0/24']))->toBeFalse();
$this->assertFalse($this->service->isIpInWhitelist('192.168.0.255', ['192.168.1.0/24'])); });
}
public function test_validates_ipv4_in_cidr_16(): void test('validates IPv4 in CIDR /16', function (): void {
{ expect($this->service->isIpInWhitelist('192.168.0.1', ['192.168.0.0/16']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('192.168.0.1', ['192.168.0.0/16'])); expect($this->service->isIpInWhitelist('192.168.255.255', ['192.168.0.0/16']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('192.168.255.255', ['192.168.0.0/16'])); });
}
public function test_rejects_ipv4_outside_cidr_16(): void test('rejects IPv4 outside CIDR /16', function (): void {
{ expect($this->service->isIpInWhitelist('192.169.0.1', ['192.168.0.0/16']))->toBeFalse();
$this->assertFalse($this->service->isIpInWhitelist('192.169.0.1', ['192.168.0.0/16'])); });
}
public function test_validates_ipv4_in_cidr_8(): void test('validates IPv4 in CIDR /8', function (): void {
{ expect($this->service->isIpInWhitelist('10.0.0.1', ['10.0.0.0/8']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('10.0.0.1', ['10.0.0.0/8'])); expect($this->service->isIpInWhitelist('10.255.255.255', ['10.0.0.0/8']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('10.255.255.255', ['10.0.0.0/8'])); });
}
public function test_rejects_ipv4_outside_cidr_8(): void test('rejects IPv4 outside CIDR /8', function (): void {
{ expect($this->service->isIpInWhitelist('11.0.0.1', ['10.0.0.0/8']))->toBeFalse();
$this->assertFalse($this->service->isIpInWhitelist('11.0.0.1', ['10.0.0.0/8'])); });
}
public function test_validates_ipv4_in_cidr_32(): void test('validates IPv4 in CIDR /32', function (): void {
{ expect($this->service->isIpInWhitelist('192.168.1.100', ['192.168.1.100/32']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('192.168.1.100', ['192.168.1.100/32'])); expect($this->service->isIpInWhitelist('192.168.1.101', ['192.168.1.100/32']))->toBeFalse();
$this->assertFalse($this->service->isIpInWhitelist('192.168.1.101', ['192.168.1.100/32'])); });
}
public function test_validates_ipv4_in_cidr_0(): void test('validates IPv4 in CIDR /0', function (): void {
{
// /0 means all IPv4 addresses // /0 means all IPv4 addresses
$this->assertTrue($this->service->isIpInWhitelist('1.2.3.4', ['0.0.0.0/0'])); expect($this->service->isIpInWhitelist('1.2.3.4', ['0.0.0.0/0']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('255.255.255.255', ['0.0.0.0/0'])); expect($this->service->isIpInWhitelist('255.255.255.255', ['0.0.0.0/0']))->toBeTrue();
} });
public function test_validates_ipv4_in_non_standard_cidr(): void test('validates IPv4 in non-standard CIDR', function (): void {
{
// /28 gives 16 addresses // /28 gives 16 addresses
$this->assertTrue($this->service->isIpInWhitelist('192.168.1.0', ['192.168.1.0/28'])); expect($this->service->isIpInWhitelist('192.168.1.0', ['192.168.1.0/28']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('192.168.1.15', ['192.168.1.0/28'])); expect($this->service->isIpInWhitelist('192.168.1.15', ['192.168.1.0/28']))->toBeTrue();
$this->assertFalse($this->service->isIpInWhitelist('192.168.1.16', ['192.168.1.0/28'])); expect($this->service->isIpInWhitelist('192.168.1.16', ['192.168.1.0/28']))->toBeFalse();
} });
// ========================================================================= test('validates IPv4 in CIDR /25', function (): void {
// /25 gives 128 addresses (0-127)
expect($this->service->isIpInWhitelist('192.168.1.0', ['192.168.1.0/25']))->toBeTrue();
expect($this->service->isIpInWhitelist('192.168.1.127', ['192.168.1.0/25']))->toBeTrue();
expect($this->service->isIpInWhitelist('192.168.1.128', ['192.168.1.0/25']))->toBeFalse();
});
test('validates IPv4 in CIDR /30', function (): void {
// /30 gives 4 addresses
expect($this->service->isIpInWhitelist('192.168.1.0', ['192.168.1.0/30']))->toBeTrue();
expect($this->service->isIpInWhitelist('192.168.1.3', ['192.168.1.0/30']))->toBeTrue();
expect($this->service->isIpInWhitelist('192.168.1.4', ['192.168.1.0/30']))->toBeFalse();
});
test('validates IPv4 in CIDR /31', function (): void {
// /31 gives 2 addresses
expect($this->service->isIpInWhitelist('192.168.1.0', ['192.168.1.0/31']))->toBeTrue();
expect($this->service->isIpInWhitelist('192.168.1.1', ['192.168.1.0/31']))->toBeTrue();
expect($this->service->isIpInWhitelist('192.168.1.2', ['192.168.1.0/31']))->toBeFalse();
});
// =============================================================================
// IPv6 Basic Tests // IPv6 Basic Tests
// ========================================================================= // =============================================================================
public function test_validates_exact_ipv6_match(): void test('validates exact IPv6 match', function (): void {
{
$result = $this->service->isIpInWhitelist('2001:db8::1', ['2001:db8::1']); $result = $this->service->isIpInWhitelist('2001:db8::1', ['2001:db8::1']);
$this->assertTrue($result); expect($result)->toBeTrue();
} });
public function test_validates_localhost_ipv6(): void test('validates localhost IPv6', function (): void {
{
$result = $this->service->isIpInWhitelist('::1', ['::1']); $result = $this->service->isIpInWhitelist('::1', ['::1']);
$this->assertTrue($result); expect($result)->toBeTrue();
} });
public function test_rejects_non_matching_ipv6(): void test('rejects non-matching IPv6', function (): void {
{
$result = $this->service->isIpInWhitelist('2001:db8::1', ['2001:db8::2']); $result = $this->service->isIpInWhitelist('2001:db8::1', ['2001:db8::2']);
$this->assertFalse($result); expect($result)->toBeFalse();
} });
public function test_normalises_ipv6_for_comparison(): void test('normalises IPv6 for comparison', function (): void {
{
// These are the same address in different formats // These are the same address in different formats
$this->assertTrue($this->service->isIpInWhitelist( $result = $this->service->isIpInWhitelist(
'2001:0db8:0000:0000:0000:0000:0000:0001', '2001:0db8:0000:0000:0000:0000:0000:0001',
['2001:db8::1'] ['2001:db8::1']
)); );
}
// ========================================================================= expect($result)->toBeTrue();
});
test('validates full IPv6 address', function (): void {
$result = $this->service->isIpInWhitelist(
'2001:0db8:85a3:0000:0000:8a2e:0370:7334',
['2001:db8:85a3::8a2e:370:7334']
);
expect($result)->toBeTrue();
});
// =============================================================================
// IPv6 CIDR Tests // IPv6 CIDR Tests
// ========================================================================= // =============================================================================
public function test_validates_ipv6_in_cidr_64(): void test('validates IPv6 in CIDR /64', function (): void {
{ expect($this->service->isIpInWhitelist(
$this->assertTrue($this->service->isIpInWhitelist(
'2001:db8:abcd:0012::1', '2001:db8:abcd:0012::1',
['2001:db8:abcd:0012::/64'] ['2001:db8:abcd:0012::/64']
)); ))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist( expect($this->service->isIpInWhitelist(
'2001:db8:abcd:0012:ffff:ffff:ffff:ffff', '2001:db8:abcd:0012:ffff:ffff:ffff:ffff',
['2001:db8:abcd:0012::/64'] ['2001:db8:abcd:0012::/64']
)); ))->toBeTrue();
} });
public function test_rejects_ipv6_outside_cidr_64(): void test('rejects IPv6 outside CIDR /64', function (): void {
{ expect($this->service->isIpInWhitelist(
$this->assertFalse($this->service->isIpInWhitelist(
'2001:db8:abcd:0013::1', '2001:db8:abcd:0013::1',
['2001:db8:abcd:0012::/64'] ['2001:db8:abcd:0012::/64']
)); ))->toBeFalse();
} });
public function test_validates_ipv6_in_cidr_32(): void test('validates IPv6 in CIDR /32', function (): void {
{ expect($this->service->isIpInWhitelist(
$this->assertTrue($this->service->isIpInWhitelist(
'2001:db8:0:0:0:0:0:1', '2001:db8:0:0:0:0:0:1',
['2001:db8::/32'] ['2001:db8::/32']
)); ))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist( expect($this->service->isIpInWhitelist(
'2001:db8:ffff:ffff:ffff:ffff:ffff:ffff', '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff',
['2001:db8::/32'] ['2001:db8::/32']
)); ))->toBeTrue();
} });
public function test_rejects_ipv6_outside_cidr_32(): void test('rejects IPv6 outside CIDR /32', function (): void {
{ expect($this->service->isIpInWhitelist(
$this->assertFalse($this->service->isIpInWhitelist(
'2001:db9::1', '2001:db9::1',
['2001:db8::/32'] ['2001:db8::/32']
)); ))->toBeFalse();
} });
public function test_validates_ipv6_in_cidr_128(): void test('validates IPv6 in CIDR /128', function (): void {
{ expect($this->service->isIpInWhitelist(
$this->assertTrue($this->service->isIpInWhitelist(
'2001:db8::1', '2001:db8::1',
['2001:db8::1/128'] ['2001:db8::1/128']
)); ))->toBeTrue();
$this->assertFalse($this->service->isIpInWhitelist( expect($this->service->isIpInWhitelist(
'2001:db8::2', '2001:db8::2',
['2001:db8::1/128'] ['2001:db8::1/128']
)); ))->toBeFalse();
} });
// ========================================================================= test('validates IPv6 in CIDR /48', function (): void {
expect($this->service->isIpInWhitelist(
'2001:db8:abcd::1',
['2001:db8:abcd::/48']
))->toBeTrue();
expect($this->service->isIpInWhitelist(
'2001:db8:abcd:ffff:ffff:ffff:ffff:ffff',
['2001:db8:abcd::/48']
))->toBeTrue();
expect($this->service->isIpInWhitelist(
'2001:db8:abce::1',
['2001:db8:abcd::/48']
))->toBeFalse();
});
test('validates IPv6 in CIDR /0', function (): void {
// /0 means all IPv6 addresses
expect($this->service->isIpInWhitelist('::1', ['::/0']))->toBeTrue();
expect($this->service->isIpInWhitelist('2001:db8::1', ['::/0']))->toBeTrue();
expect($this->service->isIpInWhitelist('fe80::1', ['::/0']))->toBeTrue();
});
test('validates IPv6 in CIDR /56', function (): void {
// /56 is common allocation size
expect($this->service->isIpInWhitelist(
'2001:db8:ab00::1',
['2001:db8:ab00::/56']
))->toBeTrue();
expect($this->service->isIpInWhitelist(
'2001:db8:ab00:ff::1',
['2001:db8:ab00::/56']
))->toBeTrue();
expect($this->service->isIpInWhitelist(
'2001:db8:ab01::1',
['2001:db8:ab00::/56']
))->toBeFalse();
});
// =============================================================================
// IPv4/IPv6 Mixed Tests // IPv4/IPv6 Mixed Tests
// ========================================================================= // =============================================================================
public function test_ipv4_does_not_match_ipv6_cidr(): void test('IPv4 does not match IPv6 CIDR', function (): void {
{ expect($this->service->isIpInWhitelist(
$this->assertFalse($this->service->isIpInWhitelist(
'192.168.1.1', '192.168.1.1',
['2001:db8::/32'] ['2001:db8::/32']
)); ))->toBeFalse();
} });
public function test_ipv6_does_not_match_ipv4_cidr(): void test('IPv6 does not match IPv4 CIDR', function (): void {
{ expect($this->service->isIpInWhitelist(
$this->assertFalse($this->service->isIpInWhitelist(
'2001:db8::1', '2001:db8::1',
['192.168.1.0/24'] ['192.168.1.0/24']
)); ))->toBeFalse();
} });
public function test_whitelist_can_contain_both_ipv4_and_ipv6(): void test('whitelist can contain both IPv4 and IPv6', function (): void {
{
$whitelist = ['192.168.1.0/24', '2001:db8::/32']; $whitelist = ['192.168.1.0/24', '2001:db8::/32'];
$this->assertTrue($this->service->isIpInWhitelist('192.168.1.100', $whitelist)); expect($this->service->isIpInWhitelist('192.168.1.100', $whitelist))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('2001:db8::1', $whitelist)); expect($this->service->isIpInWhitelist('2001:db8::1', $whitelist))->toBeTrue();
$this->assertFalse($this->service->isIpInWhitelist('10.0.0.1', $whitelist)); expect($this->service->isIpInWhitelist('10.0.0.1', $whitelist))->toBeFalse();
} });
// ========================================================================= // =============================================================================
// API Key Integration Tests // API Key Integration Tests
// ========================================================================= // =============================================================================
public function test_validate_ip_returns_true_when_restrictions_disabled(): void test('validateIp returns true when restrictions disabled', function (): void {
{
$key = AgentApiKey::generate($this->workspace, 'Test Key'); $key = AgentApiKey::generate($this->workspace, 'Test Key');
$result = $this->service->validateIp($key, '192.168.1.100'); $result = $this->service->validateIp($key, '192.168.1.100');
$this->assertTrue($result); expect($result)->toBeTrue();
} });
public function test_validate_ip_returns_false_when_enabled_with_empty_whitelist(): void test('validateIp returns false when enabled with empty whitelist', function (): void {
{
$key = AgentApiKey::generate($this->workspace, 'Test Key'); $key = AgentApiKey::generate($this->workspace, 'Test Key');
$key->enableIpRestriction(); $key->enableIpRestriction();
$result = $this->service->validateIp($key->fresh(), '192.168.1.100'); $result = $this->service->validateIp($key->fresh(), '192.168.1.100');
$this->assertFalse($result); expect($result)->toBeFalse();
} });
public function test_validate_ip_checks_whitelist(): void test('validateIp checks whitelist', function (): void {
{
$key = AgentApiKey::generate($this->workspace, 'Test Key'); $key = AgentApiKey::generate($this->workspace, 'Test Key');
$key->enableIpRestriction(); $key->enableIpRestriction();
$key->updateIpWhitelist(['192.168.1.100', '10.0.0.0/8']); $key->updateIpWhitelist(['192.168.1.100', '10.0.0.0/8']);
$fresh = $key->fresh(); $fresh = $key->fresh();
$this->assertTrue($this->service->validateIp($fresh, '192.168.1.100')); expect($this->service->validateIp($fresh, '192.168.1.100'))->toBeTrue();
$this->assertTrue($this->service->validateIp($fresh, '10.0.0.50')); expect($this->service->validateIp($fresh, '10.0.0.50'))->toBeTrue();
$this->assertFalse($this->service->validateIp($fresh, '172.16.0.1')); expect($this->service->validateIp($fresh, '172.16.0.1'))->toBeFalse();
} });
// ========================================================================= // =============================================================================
// Entry Validation Tests // Entry Validation Tests
// ========================================================================= // =============================================================================
public function test_validate_entry_accepts_valid_ipv4(): void test('validateEntry accepts valid IPv4', function (): void {
{
$result = $this->service->validateEntry('192.168.1.1'); $result = $this->service->validateEntry('192.168.1.1');
$this->assertTrue($result['valid']); expect($result['valid'])->toBeTrue();
$this->assertNull($result['error']); expect($result['error'])->toBeNull();
} });
public function test_validate_entry_accepts_valid_ipv6(): void test('validateEntry accepts valid IPv6', function (): void {
{
$result = $this->service->validateEntry('2001:db8::1'); $result = $this->service->validateEntry('2001:db8::1');
$this->assertTrue($result['valid']); expect($result['valid'])->toBeTrue();
$this->assertNull($result['error']); expect($result['error'])->toBeNull();
} });
public function test_validate_entry_accepts_valid_ipv4_cidr(): void test('validateEntry accepts valid IPv4 CIDR', function (): void {
{
$result = $this->service->validateEntry('192.168.1.0/24'); $result = $this->service->validateEntry('192.168.1.0/24');
$this->assertTrue($result['valid']); expect($result['valid'])->toBeTrue();
$this->assertNull($result['error']); expect($result['error'])->toBeNull();
} });
public function test_validate_entry_accepts_valid_ipv6_cidr(): void test('validateEntry accepts valid IPv6 CIDR', function (): void {
{
$result = $this->service->validateEntry('2001:db8::/32'); $result = $this->service->validateEntry('2001:db8::/32');
$this->assertTrue($result['valid']); expect($result['valid'])->toBeTrue();
$this->assertNull($result['error']); expect($result['error'])->toBeNull();
} });
public function test_validate_entry_rejects_empty(): void test('validateEntry rejects empty', function (): void {
{
$result = $this->service->validateEntry(''); $result = $this->service->validateEntry('');
$this->assertFalse($result['valid']); expect($result['valid'])->toBeFalse();
$this->assertEquals('Empty entry', $result['error']); expect($result['error'])->toBe('Empty entry');
} });
public function test_validate_entry_rejects_invalid_ip(): void test('validateEntry rejects invalid IP', function (): void {
{
$result = $this->service->validateEntry('not-an-ip'); $result = $this->service->validateEntry('not-an-ip');
$this->assertFalse($result['valid']); expect($result['valid'])->toBeFalse();
$this->assertEquals('Invalid IP address', $result['error']); expect($result['error'])->toBe('Invalid IP address');
} });
public function test_validate_entry_rejects_invalid_cidr(): void test('validateEntry rejects invalid CIDR', function (): void {
{
$result = $this->service->validateEntry('192.168.1.0/'); $result = $this->service->validateEntry('192.168.1.0/');
$this->assertFalse($result['valid']); expect($result['valid'])->toBeFalse();
} });
// ========================================================================= // =============================================================================
// CIDR Validation Tests // CIDR Validation Tests
// ========================================================================= // =============================================================================
public function test_validate_cidr_accepts_valid_ipv4_prefixes(): void test('validateCidr accepts valid IPv4 prefixes', function (): void {
{ expect($this->service->validateCidr('192.168.1.0/0')['valid'])->toBeTrue();
$this->assertTrue($this->service->validateCidr('192.168.1.0/0')['valid']); expect($this->service->validateCidr('192.168.1.0/16')['valid'])->toBeTrue();
$this->assertTrue($this->service->validateCidr('192.168.1.0/16')['valid']); expect($this->service->validateCidr('192.168.1.0/32')['valid'])->toBeTrue();
$this->assertTrue($this->service->validateCidr('192.168.1.0/32')['valid']); });
}
public function test_validate_cidr_rejects_invalid_ipv4_prefixes(): void test('validateCidr rejects invalid IPv4 prefixes', function (): void {
{
$result = $this->service->validateCidr('192.168.1.0/33'); $result = $this->service->validateCidr('192.168.1.0/33');
$this->assertFalse($result['valid']); expect($result['valid'])->toBeFalse();
$this->assertStringContainsString('IPv4 prefix must be', $result['error']); expect($result['error'])->toContain('IPv4 prefix must be');
} });
public function test_validate_cidr_accepts_valid_ipv6_prefixes(): void test('validateCidr accepts valid IPv6 prefixes', function (): void {
{ expect($this->service->validateCidr('2001:db8::/0')['valid'])->toBeTrue();
$this->assertTrue($this->service->validateCidr('2001:db8::/0')['valid']); expect($this->service->validateCidr('2001:db8::/64')['valid'])->toBeTrue();
$this->assertTrue($this->service->validateCidr('2001:db8::/64')['valid']); expect($this->service->validateCidr('2001:db8::/128')['valid'])->toBeTrue();
$this->assertTrue($this->service->validateCidr('2001:db8::/128')['valid']); });
}
public function test_validate_cidr_rejects_invalid_ipv6_prefixes(): void test('validateCidr rejects invalid IPv6 prefixes', function (): void {
{
$result = $this->service->validateCidr('2001:db8::/129'); $result = $this->service->validateCidr('2001:db8::/129');
$this->assertFalse($result['valid']); expect($result['valid'])->toBeFalse();
$this->assertStringContainsString('IPv6 prefix must be', $result['error']); expect($result['error'])->toContain('IPv6 prefix must be');
} });
public function test_validate_cidr_rejects_negative_prefix(): void test('validateCidr rejects negative prefix', function (): void {
{
$result = $this->service->validateCidr('192.168.1.0/-1'); $result = $this->service->validateCidr('192.168.1.0/-1');
$this->assertFalse($result['valid']); expect($result['valid'])->toBeFalse();
} });
public function test_validate_cidr_rejects_non_numeric_prefix(): void test('validateCidr rejects non-numeric prefix', function (): void {
{
$result = $this->service->validateCidr('192.168.1.0/abc'); $result = $this->service->validateCidr('192.168.1.0/abc');
$this->assertFalse($result['valid']); expect($result['valid'])->toBeFalse();
$this->assertEquals('Invalid prefix length', $result['error']); expect($result['error'])->toBe('Invalid prefix length');
} });
public function test_validate_cidr_rejects_invalid_ip_in_cidr(): void test('validateCidr rejects invalid IP in CIDR', function (): void {
{
$result = $this->service->validateCidr('invalid/24'); $result = $this->service->validateCidr('invalid/24');
$this->assertFalse($result['valid']); expect($result['valid'])->toBeFalse();
$this->assertEquals('Invalid IP address in CIDR', $result['error']); expect($result['error'])->toBe('Invalid IP address in CIDR');
} });
// ========================================================================= // =============================================================================
// Parse Whitelist Input Tests // Parse Whitelist Input Tests
// ========================================================================= // =============================================================================
public function test_parse_whitelist_input_handles_newlines(): void test('parseWhitelistInput handles newlines', function (): void {
{
$input = "192.168.1.1\n192.168.1.2\n192.168.1.3"; $input = "192.168.1.1\n192.168.1.2\n192.168.1.3";
$result = $this->service->parseWhitelistInput($input); $result = $this->service->parseWhitelistInput($input);
$this->assertCount(3, $result['entries']); expect($result['entries'])->toHaveCount(3);
$this->assertEmpty($result['errors']); expect($result['errors'])->toBeEmpty();
} });
public function test_parse_whitelist_input_handles_commas(): void test('parseWhitelistInput handles commas', function (): void {
{
$input = '192.168.1.1,192.168.1.2,192.168.1.3'; $input = '192.168.1.1,192.168.1.2,192.168.1.3';
$result = $this->service->parseWhitelistInput($input); $result = $this->service->parseWhitelistInput($input);
$this->assertCount(3, $result['entries']); expect($result['entries'])->toHaveCount(3);
} });
public function test_parse_whitelist_input_handles_carriage_returns(): void test('parseWhitelistInput handles carriage returns', function (): void {
{
$input = "192.168.1.1\r\n192.168.1.2\r\n192.168.1.3"; $input = "192.168.1.1\r\n192.168.1.2\r\n192.168.1.3";
$result = $this->service->parseWhitelistInput($input); $result = $this->service->parseWhitelistInput($input);
$this->assertCount(3, $result['entries']); expect($result['entries'])->toHaveCount(3);
} });
public function test_parse_whitelist_input_trims_whitespace(): void test('parseWhitelistInput trims whitespace', function (): void {
{
$input = " 192.168.1.1 \n 192.168.1.2 "; $input = " 192.168.1.1 \n 192.168.1.2 ";
$result = $this->service->parseWhitelistInput($input); $result = $this->service->parseWhitelistInput($input);
$this->assertContains('192.168.1.1', $result['entries']); expect($result['entries'])->toContain('192.168.1.1');
$this->assertContains('192.168.1.2', $result['entries']); expect($result['entries'])->toContain('192.168.1.2');
} });
public function test_parse_whitelist_input_skips_empty_lines(): void test('parseWhitelistInput skips empty lines', function (): void {
{
$input = "192.168.1.1\n\n\n192.168.1.2"; $input = "192.168.1.1\n\n\n192.168.1.2";
$result = $this->service->parseWhitelistInput($input); $result = $this->service->parseWhitelistInput($input);
$this->assertCount(2, $result['entries']); expect($result['entries'])->toHaveCount(2);
} });
public function test_parse_whitelist_input_skips_comments(): void test('parseWhitelistInput skips comments', function (): void {
{
$input = "# This is a comment\n192.168.1.1\n# Another comment\n192.168.1.2"; $input = "# This is a comment\n192.168.1.1\n# Another comment\n192.168.1.2";
$result = $this->service->parseWhitelistInput($input); $result = $this->service->parseWhitelistInput($input);
$this->assertCount(2, $result['entries']); expect($result['entries'])->toHaveCount(2);
$this->assertNotContains('# This is a comment', $result['entries']); expect($result['entries'])->not->toContain('# This is a comment');
} });
public function test_parse_whitelist_input_collects_errors(): void test('parseWhitelistInput collects errors', function (): void {
{
$input = "192.168.1.1\ninvalid\n192.168.1.2\nalso-invalid"; $input = "192.168.1.1\ninvalid\n192.168.1.2\nalso-invalid";
$result = $this->service->parseWhitelistInput($input); $result = $this->service->parseWhitelistInput($input);
$this->assertCount(2, $result['entries']); expect($result['entries'])->toHaveCount(2);
$this->assertCount(2, $result['errors']); expect($result['errors'])->toHaveCount(2);
} });
// ========================================================================= test('parseWhitelistInput handles mixed content', function (): void {
$input = "# Office IPs\n192.168.1.0/24\n# Cloud provider\n10.0.0.0/8\n# Invalid\ninvalid-ip";
$result = $this->service->parseWhitelistInput($input);
expect($result['entries'])->toHaveCount(2);
expect($result['entries'])->toContain('192.168.1.0/24');
expect($result['entries'])->toContain('10.0.0.0/8');
expect($result['errors'])->toHaveCount(1);
});
// =============================================================================
// Format Whitelist Tests // Format Whitelist Tests
// ========================================================================= // =============================================================================
public function test_format_whitelist_for_display_joins_with_newlines(): void test('formatWhitelistForDisplay joins with newlines', function (): void {
{
$whitelist = ['192.168.1.1', '10.0.0.0/8', '2001:db8::/32']; $whitelist = ['192.168.1.1', '10.0.0.0/8', '2001:db8::/32'];
$result = $this->service->formatWhitelistForDisplay($whitelist); $result = $this->service->formatWhitelistForDisplay($whitelist);
$this->assertEquals("192.168.1.1\n10.0.0.0/8\n2001:db8::/32", $result); expect($result)->toBe("192.168.1.1\n10.0.0.0/8\n2001:db8::/32");
} });
public function test_format_whitelist_for_display_handles_empty(): void test('formatWhitelistForDisplay handles empty', function (): void {
{
$result = $this->service->formatWhitelistForDisplay([]); $result = $this->service->formatWhitelistForDisplay([]);
$this->assertEquals('', $result); expect($result)->toBe('');
} });
// ========================================================================= // =============================================================================
// Describe CIDR Tests // Describe CIDR Tests
// ========================================================================= // =============================================================================
public function test_describe_cidr_for_ipv4(): void test('describeCidr for IPv4', function (): void {
{ expect($this->service->describeCidr('192.168.1.0/24'))->toContain('256 addresses');
$this->assertStringContainsString('256 addresses', $this->service->describeCidr('192.168.1.0/24')); expect($this->service->describeCidr('192.168.1.0/32'))->toContain('1 addresses');
$this->assertStringContainsString('1 addresses', $this->service->describeCidr('192.168.1.0/32')); expect($this->service->describeCidr('192.168.1.0/0'))->toContain('4294967296 addresses');
} });
public function test_describe_cidr_for_ipv6(): void test('describeCidr for IPv6', function (): void {
{
$result = $this->service->describeCidr('2001:db8::/32'); $result = $this->service->describeCidr('2001:db8::/32');
$this->assertStringContainsString('2001:db8::/32', $result); expect($result)->toContain('2001:db8::/32');
$this->assertStringContainsString('addresses', $result); expect($result)->toContain('addresses');
} });
public function test_describe_cidr_returns_original_for_invalid(): void test('describeCidr returns original for invalid', function (): void {
{
$result = $this->service->describeCidr('invalid'); $result = $this->service->describeCidr('invalid');
$this->assertEquals('invalid', $result); expect($result)->toBe('invalid');
} });
// ========================================================================= // =============================================================================
// Normalise IP Tests // Normalise IP Tests
// ========================================================================= // =============================================================================
public function test_normalise_ip_returns_same_for_ipv4(): void test('normaliseIp returns same for IPv4', function (): void {
{
$result = $this->service->normaliseIp('192.168.1.1'); $result = $this->service->normaliseIp('192.168.1.1');
$this->assertEquals('192.168.1.1', $result); expect($result)->toBe('192.168.1.1');
} });
public function test_normalise_ip_compresses_ipv6(): void test('normaliseIp compresses IPv6', function (): void {
{
$result = $this->service->normaliseIp('2001:0db8:0000:0000:0000:0000:0000:0001'); $result = $this->service->normaliseIp('2001:0db8:0000:0000:0000:0000:0000:0001');
$this->assertEquals('2001:db8::1', $result); expect($result)->toBe('2001:db8::1');
} });
public function test_normalise_ip_returns_original_for_invalid(): void test('normaliseIp returns original for invalid', function (): void {
{
$result = $this->service->normaliseIp('invalid'); $result = $this->service->normaliseIp('invalid');
$this->assertEquals('invalid', $result); expect($result)->toBe('invalid');
} });
// ========================================================================= test('normaliseIp handles trimming', function (): void {
$result = $this->service->normaliseIp(' 192.168.1.1 ');
expect($result)->toBe('192.168.1.1');
});
// =============================================================================
// Edge Cases // Edge Cases
// ========================================================================= // =============================================================================
public function test_handles_trimmed_whitelist_entries(): void test('handles trimmed whitelist entries', function (): void {
{
$result = $this->service->isIpInWhitelist('192.168.1.1', [' 192.168.1.1 ']); $result = $this->service->isIpInWhitelist('192.168.1.1', [' 192.168.1.1 ']);
$this->assertTrue($result); expect($result)->toBeTrue();
} });
public function test_skips_empty_whitelist_entries(): void test('skips empty whitelist entries', function (): void {
{
$result = $this->service->isIpInWhitelist('192.168.1.1', ['', '192.168.1.1', '']); $result = $this->service->isIpInWhitelist('192.168.1.1', ['', '192.168.1.1', '']);
$this->assertTrue($result); expect($result)->toBeTrue();
} });
public function test_returns_false_for_empty_whitelist(): void test('returns false for empty whitelist', function (): void {
{
$result = $this->service->isIpInWhitelist('192.168.1.1', []); $result = $this->service->isIpInWhitelist('192.168.1.1', []);
$this->assertFalse($result); expect($result)->toBeFalse();
} });
public function test_handles_loopback_addresses(): void test('handles loopback addresses', function (): void {
{ expect($this->service->isIpInWhitelist('127.0.0.1', ['127.0.0.0/8']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('127.0.0.1', ['127.0.0.0/8'])); expect($this->service->isIpInWhitelist('::1', ['::1']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('::1', ['::1'])); });
}
public function test_handles_private_ranges(): void test('handles private ranges', function (): void {
{
// RFC 1918 private ranges // RFC 1918 private ranges
$this->assertTrue($this->service->isIpInWhitelist('10.0.0.1', ['10.0.0.0/8'])); expect($this->service->isIpInWhitelist('10.0.0.1', ['10.0.0.0/8']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('172.16.0.1', ['172.16.0.0/12'])); expect($this->service->isIpInWhitelist('172.16.0.1', ['172.16.0.0/12']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('192.168.0.1', ['192.168.0.0/16'])); expect($this->service->isIpInWhitelist('192.168.0.1', ['192.168.0.0/16']))->toBeTrue();
} });
public function test_handles_link_local_ipv6(): void test('handles link-local IPv6', function (): void {
{ expect($this->service->isIpInWhitelist('fe80::1', ['fe80::/10']))->toBeTrue();
$this->assertTrue($this->service->isIpInWhitelist('fe80::1', ['fe80::/10'])); });
}
} test('handles unique local IPv6', function (): void {
expect($this->service->isIpInWhitelist('fd00::1', ['fc00::/7']))->toBeTrue();
});
test('rejects malformed CIDR', function (): void {
expect($this->service->ipMatchesCidr('192.168.1.1', '192.168.1.0'))->toBeFalse();
expect($this->service->ipMatchesCidr('192.168.1.1', '192.168.1.0//'))->toBeFalse();
});
test('handles multiple CIDR ranges in whitelist', function (): void {
$whitelist = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'2001:db8::/32',
];
expect($this->service->isIpInWhitelist('10.1.2.3', $whitelist))->toBeTrue();
expect($this->service->isIpInWhitelist('172.20.1.1', $whitelist))->toBeTrue();
expect($this->service->isIpInWhitelist('192.168.100.50', $whitelist))->toBeTrue();
expect($this->service->isIpInWhitelist('2001:db8:1234::1', $whitelist))->toBeTrue();
expect($this->service->isIpInWhitelist('8.8.8.8', $whitelist))->toBeFalse();
});
test('handles boundary IPs in CIDR range', function (): void {
// First and last IP in a /24
expect($this->service->isIpInWhitelist('192.168.1.0', ['192.168.1.0/24']))->toBeTrue();
expect($this->service->isIpInWhitelist('192.168.1.255', ['192.168.1.0/24']))->toBeTrue();
// Just outside the range
expect($this->service->isIpInWhitelist('192.168.0.255', ['192.168.1.0/24']))->toBeFalse();
expect($this->service->isIpInWhitelist('192.168.2.0', ['192.168.1.0/24']))->toBeFalse();
});
test('handles very large IPv6 ranges', function (): void {
// /16 gives an enormous number of addresses
expect($this->service->isIpInWhitelist('2001:db8::1', ['2001::/16']))->toBeTrue();
expect($this->service->isIpInWhitelist('2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff', ['2001::/16']))->toBeTrue();
expect($this->service->isIpInWhitelist('2002::1', ['2001::/16']))->toBeFalse();
});