dd/tests/Unit/Freenode/CodecTest.php

101 lines
4.5 KiB
PHP

<?php
namespace Tests\Unit\Freenode;
use App\Services\Freenode\Codec;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
final class CodecTest extends TestCase
{
private const UUID = '00000000-0000-0000-0000-000000000001';
public function testEncodedParametersSurviveRoundtrip(): void
{
$uri = 'vless://'.self::UUID.'@example.invalid:443?type=ws&path=%2Fa%26b%2Bc%23d%3Fq%3D1&sni=tls.example.invalid&security=tls#name';
$node = Codec::parse($uri);
$out = Codec::singbox($node, 'test');
self::assertSame('/a&b+c#d?q=1', $out['transport']['path']);
self::assertSame('tls.example.invalid', $out['tls']['server_name']);
self::assertSame($node['id'], Codec::parse(Codec::uri($node, 'new name'))['id']);
}
public function testIpv6Endpoint(): void
{
$node = Codec::parse('vless://'.self::UUID.'@[2001:db8::1]:443?security=tls#test');
$out = Codec::singbox($node, 'test');
self::assertSame('2001:db8::1', $out['server']);
self::assertSame(443, $out['server_port']);
}
public function testVmessAndBom(): void
{
$config = ['add' => 'example.invalid', 'port' => '443', 'id' => self::UUID, 'net' => 'ws', 'host' => 'cdn.example.invalid', 'sni' => 'tls.example.invalid', 'tls' => 'tls', 'ps' => 'original'];
$node = Codec::parse("\xEF\xBB\xBFvmess://".base64_encode(json_encode($config)));
$uri = Codec::uri($node, '中文 + name');
self::assertSame('中文 + name', json_decode(Codec::decode64(substr($uri, 8)), true)['ps']);
self::assertSame('tls.example.invalid', Codec::singbox($node, 'x')['tls']['server_name']);
}
public function testPasswordCharactersAreNotDecodedTwice(): void
{
$node = Codec::parse('trojan://a%2Bb%40c%23d@example.invalid:443?security=tls#test');
self::assertSame('a+b@c#d', Codec::singbox($node, 'x')['password']);
}
public function testRawPasswordBracketsAreEscapedInPublishedUri(): void
{
$node = Codec::parse('trojan://pass[word]@example.invalid:443?security=tls#test');
$uri = Codec::uri($node, 'test');
self::assertStringStartsWith('trojan://pass%5Bword%5D@example.invalid:443?', $uri);
self::assertSame('pass[word]', Codec::singbox($node, 'test')['password']);
self::assertSame($node['id'], Codec::parse($uri)['id']);
}
public function testEscapedUserinfoKeepsExactDecodedPassword(): void
{
$password = 'a[b]+c@d#e:f%2F';
$node = Codec::parse('trojan://'.rawurlencode($password).'@example.invalid:443?security=tls#test');
$uri = Codec::uri($node, 'test');
self::assertStringStartsWith('trojan://'.rawurlencode($password).'@', $uri);
self::assertSame($password, Codec::singbox(Codec::parse($uri), 'test')['password']);
self::assertSame($node['id'], Codec::parse($uri)['id']);
}
public function testPasswordBracketsDoNotChangeIpv6Host(): void
{
$node = Codec::parse('trojan://a[b]@[2001:db8::1]:443?security=tls#test');
self::assertStringStartsWith('trojan://a%5Bb%5D@[2001:db8::1]:443?', Codec::uri($node, 'test'));
self::assertSame('2001:db8::1', Codec::singbox($node, 'test')['server']);
}
public function testSsBase64UrlAndPlugin(): void
{
$auth = rtrim(strtr(base64_encode('aes-128-gcm:a+b'), '+/', '-_'), '=');
$node = Codec::parse('ss://'.$auth.'@example.invalid:443?plugin=v2ray-plugin%3Btls%3Bhost%3Dexample.invalid#test');
$out = Codec::singbox($node, 'x');
self::assertSame('a+b', $out['password']);
self::assertSame('v2ray-plugin', $out['plugin']);
self::assertSame('tls;host=example.invalid', $out['plugin_opts']);
self::assertSame($node['id'], Codec::parse(Codec::uri($node, 'new'))['id']);
}
public function testQueryOrderAndNamesDoNotChangeIdentity(): void
{
$a = Codec::parse('vless://'.self::UUID.'@example.invalid:443?type=ws&path=%2Fx#first');
$b = Codec::parse('vless://'.self::UUID.'@example.invalid:443?path=%2Fx&type=ws#second');
self::assertSame($a['id'], $b['id']);
}
public function testUnknownTransportIsNotSilentlyConverted(): void
{
$node = Codec::parse('vless://'.self::UUID.'@example.invalid:443?type=xhttp#test');
$this->expectException(InvalidArgumentException::class);
Codec::singbox($node, 'x');
}
public function testInvalidPortIsRejected(): void
{
$this->expectException(InvalidArgumentException::class);
Codec::parse('socks://example.invalid:0#test');
}
}