key = base64_encode(str_repeat("\0", 32)); } public function testCredentialsUseNordVpnAccessTokenAuthorization(): void { $api = new NordApi(function ($path, $query, $authorization): array { $this->assertSame('/v1/users/services/credentials', $path); $this->assertSame([], $query); $this->assertSame('Bearer token:synthetic-token', $authorization); return ['nordlynx_private_key' => $this->key]; }); $this->assertSame($this->key, $api->credentials('synthetic-token')); } public function testRecommendationsApplyCountryAllowlist(): void { $requests = []; $api = new NordApi(function ($path, $query) use (&$requests): array { $requests[] = [$path, $query]; if ($path === '/v1/servers/countries') { return [['id' => 99, 'code' => 'nl', 'name' => 'Netherlands']]; } return [[ 'hostname' => 'nl.example.invalid', 'station' => '198.51.100.10', 'locations' => [['country' => ['code' => 'nl']]], 'technologies' => [[ 'id' => 35, 'pivot' => ['status' => 'online'], 'metadata' => [['name' => 'public_key', 'value' => $this->key]], ]], ]]; }); $servers = $api->recommendations(['NL']); $this->assertCount(1, $servers); $this->assertSame('NL', $servers[0]['country']); $this->assertSame(99, $requests[1][1]['filters[country_id]']); $this->assertSame(35, $requests[1][1]['filters[servers_technologies]']); } public function testEmptyCountryAllowlistQueriesRecommendationsDirectly(): void { $api = new NordApi(static function ($path): array { if ($path === '/v1/servers/countries') { throw new RuntimeException('countries should not be queried'); } return []; }); $this->assertSame([], $api->recommendations([])); } public function testRecommendationsRequestEverySelectedCountry(): void { $requests = []; $api = new NordApi(function ($path, $query) use (&$requests): array { $requests[] = [$path, $query]; if ($path === '/v1/servers/countries') { return [ ['id' => 1, 'code' => 'nl', 'name' => 'Netherlands'], ['id' => 2, 'code' => 'kr', 'name' => 'Korea'], ]; } $country = $query['filters[country_id]'] === 1 ? 'NL' : 'KR'; return [[ 'hostname' => strtolower($country) . '.example.invalid', 'station' => $country === 'NL' ? '198.51.100.10' : '198.51.100.11', 'locations' => [['country' => ['code' => $country]]], 'technologies' => [[ 'id' => 35, 'pivot' => ['status' => 'online'], 'metadata' => [['name' => 'public_key', 'value' => $this->key]], ]], ]]; }); $servers = $api->recommendations(['NL', 'KR'], 1); self::assertSame(1, $requests[1][1]['filters[country_id]']); self::assertSame(2, $requests[2][1]['filters[country_id]']); self::assertSame(['NL', 'KR'], array_column($servers, 'country')); } public function testEndpointDiscoveryPaginatesCompleteNordLynxAddressSet(): void { $requests = []; $api = new NordApi(static function ($path, $query) use (&$requests): array { $requests[] = [$path, $query]; if ($query['offset'] === 0) { return array_map( static fn($index) => ['station' => sprintf('198.51.%d.%d', intdiv($index, 250), $index % 250 + 1)], range(0, 999) ); } return [['station' => '203.0.113.10'], ['station' => 'invalid']]; }); $addresses = $api->endpointAddresses(); $this->assertCount(1001, $addresses); $this->assertSame('/v1/servers', $requests[0][0]); $this->assertSame(1000, $requests[0][1]['limit']); $this->assertSame(1000, $requests[1][1]['offset']); $this->assertSame(35, $requests[0][1]['filters[servers_technologies]']); } }