lthn.io/app/Core/Tests/Unit/PrivacyHelperTest.php
Claude 41a90cbff8
feat: lthn.io API serving live chain data
Fixed: basePath self→static binding, namespace detection, event wiring,
SQLite cache, file cache driver. All Mod Boot classes converted to
$listens pattern for lifecycle event discovery.

Working endpoints:
- /v1/explorer/info — live chain height, difficulty, aliases
- /v1/explorer/stats — formatted chain statistics
- /v1/names/directory — alias directory grouped by type
- /v1/names/available/{name} — name availability check
- /v1/names/lookup/{name} — name details

Co-Authored-By: Charon <charon@lethean.io>
2026-04-03 17:17:42 +01:00

282 lines
8.6 KiB
PHP

<?php
/*
* Core PHP Framework
*
* Licensed under the European Union Public Licence (EUPL) v1.2.
* See LICENSE file for details.
*/
declare(strict_types=1);
namespace Core\Tests\Unit;
use Core\Helpers\PrivacyHelper;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class PrivacyHelperTest extends TestCase
{
// -------------------------------------------------------------------------
// IP Anonymisation (Standard)
// -------------------------------------------------------------------------
#[Test]
public function it_anonymises_ipv4_by_zeroing_last_octet(): void
{
$this->assertSame('192.168.1.0', PrivacyHelper::anonymiseIp('192.168.1.123'));
$this->assertSame('10.0.0.0', PrivacyHelper::anonymiseIp('10.0.0.255'));
$this->assertSame('8.8.8.0', PrivacyHelper::anonymiseIp('8.8.8.8'));
}
#[Test]
public function it_anonymises_ipv6_by_zeroing_last_80_bits(): void
{
$result = PrivacyHelper::anonymiseIp('2001:0db8:85a3:0000:0000:8a2e:0370:7334');
// Should keep first 3 groups, zero the rest
$this->assertStringStartsWith('2001:0db8:85a3:', $result);
$this->assertStringEndsWith(':0000:0000:0000:0000:0000', $result);
}
#[Test]
public function it_handles_abbreviated_ipv6(): void
{
$result = PrivacyHelper::anonymiseIp('2001:db8::1');
$this->assertNotNull($result);
// Should expand and anonymise
$this->assertStringContainsString('2001', $result);
}
#[Test]
public function it_returns_null_for_null_ip(): void
{
$this->assertNull(PrivacyHelper::anonymiseIp(null));
}
#[Test]
public function it_returns_null_for_empty_ip(): void
{
$this->assertNull(PrivacyHelper::anonymiseIp(''));
}
#[Test]
public function it_returns_null_for_invalid_ip(): void
{
$this->assertNull(PrivacyHelper::anonymiseIp('not-an-ip'));
$this->assertNull(PrivacyHelper::anonymiseIp('999.999.999.999'));
}
// -------------------------------------------------------------------------
// IP Anonymisation (Strong)
// -------------------------------------------------------------------------
#[Test]
public function it_anonymises_ipv4_strongly_by_zeroing_last_two_octets(): void
{
$this->assertSame('192.168.0.0', PrivacyHelper::anonymiseIpStrong('192.168.1.123'));
$this->assertSame('10.0.0.0', PrivacyHelper::anonymiseIpStrong('10.0.0.255'));
$this->assertSame('8.8.0.0', PrivacyHelper::anonymiseIpStrong('8.8.8.8'));
}
#[Test]
public function it_anonymises_ipv6_strongly_by_zeroing_last_96_bits(): void
{
$result = PrivacyHelper::anonymiseIpStrong('2001:0db8:85a3:0000:0000:8a2e:0370:7334');
// Should keep first 2 groups, zero the rest
$this->assertStringStartsWith('2001:0db8:', $result);
$parts = explode(':', $result);
// Last 6 groups should be zeroed
for ($i = 2; $i < 8; $i++) {
$this->assertSame('0000', $parts[$i]);
}
}
#[Test]
public function it_handles_null_for_strong_anonymisation(): void
{
$this->assertNull(PrivacyHelper::anonymiseIpStrong(null));
$this->assertNull(PrivacyHelper::anonymiseIpStrong(''));
}
// -------------------------------------------------------------------------
// IP Hashing (Daily)
// -------------------------------------------------------------------------
#[Test]
public function it_hashes_ip_with_daily_rotation(): void
{
$hash = PrivacyHelper::hashIpDaily('192.168.1.1');
$this->assertNotNull($hash);
$this->assertSame(64, strlen($hash)); // SHA256 = 64 hex chars
}
#[Test]
public function it_produces_same_hash_for_same_ip_on_same_day(): void
{
$hash1 = PrivacyHelper::hashIpDaily('192.168.1.1');
$hash2 = PrivacyHelper::hashIpDaily('192.168.1.1');
$this->assertSame($hash1, $hash2);
}
#[Test]
public function it_produces_different_hashes_for_different_ips(): void
{
$hash1 = PrivacyHelper::hashIpDaily('192.168.1.1');
$hash2 = PrivacyHelper::hashIpDaily('192.168.1.2');
$this->assertNotSame($hash1, $hash2);
}
#[Test]
public function it_accepts_custom_salt_for_daily_hash(): void
{
$hash1 = PrivacyHelper::hashIpDaily('192.168.1.1', 'custom-salt');
$hash2 = PrivacyHelper::hashIpDaily('192.168.1.1', 'different-salt');
$this->assertNotSame($hash1, $hash2);
}
#[Test]
public function it_returns_null_for_null_ip_daily_hash(): void
{
$this->assertNull(PrivacyHelper::hashIpDaily(null));
}
// -------------------------------------------------------------------------
// IP Hashing (Static)
// -------------------------------------------------------------------------
#[Test]
public function it_hashes_ip_with_static_salt(): void
{
$hash = PrivacyHelper::hashIp('192.168.1.1');
$this->assertNotNull($hash);
$this->assertSame(64, strlen($hash));
}
#[Test]
public function it_produces_consistent_static_hash(): void
{
$hash1 = PrivacyHelper::hashIp('192.168.1.1');
$hash2 = PrivacyHelper::hashIp('192.168.1.1');
$this->assertSame($hash1, $hash2);
}
#[Test]
public function it_returns_null_for_null_ip_static_hash(): void
{
$this->assertNull(PrivacyHelper::hashIp(null));
}
// -------------------------------------------------------------------------
// Unique Visitor Cache Key
// -------------------------------------------------------------------------
#[Test]
public function it_generates_unique_visitor_cache_key(): void
{
$key = PrivacyHelper::uniqueVisitorCacheKey('page:123', '192.168.1.1');
$this->assertStringStartsWith('page:123:', $key);
$this->assertStringContainsString('192.168.1.1', $key);
$this->assertStringContainsString(now()->format('Y-m-d'), $key);
}
#[Test]
public function it_generates_same_key_for_same_visitor_same_day(): void
{
$key1 = PrivacyHelper::uniqueVisitorCacheKey('page:123', '192.168.1.1');
$key2 = PrivacyHelper::uniqueVisitorCacheKey('page:123', '192.168.1.1');
$this->assertSame($key1, $key2);
}
#[Test]
public function it_generates_different_keys_for_different_prefixes(): void
{
$key1 = PrivacyHelper::uniqueVisitorCacheKey('page:123', '192.168.1.1');
$key2 = PrivacyHelper::uniqueVisitorCacheKey('page:456', '192.168.1.1');
$this->assertNotSame($key1, $key2);
}
// -------------------------------------------------------------------------
// Private IP Detection
// -------------------------------------------------------------------------
#[Test]
#[DataProvider('privateIpAddresses')]
public function it_detects_private_ips(string $ip): void
{
$this->assertTrue(PrivacyHelper::isPrivateIp($ip));
}
public static function privateIpAddresses(): array
{
return [
'localhost' => ['127.0.0.1'],
'private 10.x.x.x' => ['10.0.0.1'],
'private 172.16.x.x' => ['172.16.0.1'],
'private 192.168.x.x' => ['192.168.1.1'],
'link-local' => ['169.254.1.1'],
];
}
#[Test]
#[DataProvider('publicIpAddresses')]
public function it_detects_public_ips(string $ip): void
{
$this->assertFalse(PrivacyHelper::isPrivateIp($ip));
}
public static function publicIpAddresses(): array
{
return [
'google dns' => ['8.8.8.8'],
'cloudflare dns' => ['1.1.1.1'],
'random public' => ['203.0.113.1'],
];
}
#[Test]
public function it_treats_null_ip_as_private(): void
{
$this->assertTrue(PrivacyHelper::isPrivateIp(null));
}
#[Test]
public function it_treats_empty_ip_as_private(): void
{
$this->assertTrue(PrivacyHelper::isPrivateIp(''));
}
// -------------------------------------------------------------------------
// Edge Cases
// -------------------------------------------------------------------------
#[Test]
public function it_handles_ipv4_mapped_ipv6(): void
{
// ::ffff:192.168.1.1 is IPv4-mapped IPv6
$result = PrivacyHelper::anonymiseIp('::ffff:192.168.1.1');
// Should be handled as IPv6
$this->assertNotNull($result);
}
#[Test]
public function it_handles_loopback_ipv6(): void
{
$result = PrivacyHelper::anonymiseIp('::1');
$this->assertNotNull($result);
}
}