399 lines
15 KiB
PHP
399 lines
15 KiB
PHP
|
<?php
|
||
|
// HttpUriTest.php
|
||
|
// Created: 2025-02-28
|
||
|
// Updated: 2025-02-28
|
||
|
|
||
|
declare(strict_types=1);
|
||
|
|
||
|
use Index\Http\HttpUri;
|
||
|
use PHPUnit\Framework\TestCase;
|
||
|
use PHPUnit\Framework\Attributes\{CoversClass,DataProvider};
|
||
|
use Psr\Http\Message\UriInterface;
|
||
|
|
||
|
// based on https://github.com/bakame-php/psr7-uri-interface-tests/blob/5a556fdfe668a6c6a14772efeba6134c0b7dae34/tests/AbstractUriTestCase.php
|
||
|
#[CoversClass(HttpUri::class)]
|
||
|
final class HttpUriTest extends TestCase {
|
||
|
private const string URI = 'http://username:pwd@secure.example.com:443/meow/soap.php?soup=beans#mewow';
|
||
|
private HttpUri $uri;
|
||
|
|
||
|
protected function setUp(): void {
|
||
|
$this->uri = HttpUri::createUri(self::URI);
|
||
|
}
|
||
|
|
||
|
public static function schemeProvider(): array {
|
||
|
return [
|
||
|
['HtTpS', 'https'], // normalized scheme
|
||
|
['http', 'http'], // simple scheme
|
||
|
['', ''], // no scheme
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('schemeProvider')]
|
||
|
public function testGetScheme(string $scheme, string $expected): void {
|
||
|
$uri = $this->uri->withScheme($scheme);
|
||
|
$this->assertInstanceOf(UriInterface::class, $uri);
|
||
|
$this->assertSame($expected, $uri->getScheme());
|
||
|
$this->assertInstanceOf(HttpUri::class, $uri);
|
||
|
$this->assertSame($expected, $uri->scheme);
|
||
|
}
|
||
|
|
||
|
public static function userInfoProvider(): array {
|
||
|
return [
|
||
|
['FreakyFurball', 'Express1', 'FreakyFurball:Express1'], // with userinfo
|
||
|
['', '', ''], // no userinfo
|
||
|
['Unko', '', 'Unko:'], // no pass
|
||
|
['flash', null, 'flash'], // pass is null
|
||
|
['Reemo', 'BuddyMan5', 'Reemo:BuddyMan5'], // case sensitive
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('userInfoProvider')]
|
||
|
public function testGetUserInfo(string $user, ?string $pass, string $expected): void {
|
||
|
$uri = $this->uri->withUserInfo($user, $pass);
|
||
|
$this->assertInstanceOf(UriInterface::class, $uri);
|
||
|
$this->assertSame($expected, $uri->getUserInfo());
|
||
|
$this->assertInstanceOf(HttpUri::class, $uri);
|
||
|
$this->assertSame($expected, $uri->userInfo);
|
||
|
}
|
||
|
|
||
|
public static function hostProvider(): array {
|
||
|
return [
|
||
|
['MaStEr.eXaMpLe.CoM', 'master.example.com'], // normalized host
|
||
|
['www.example.com', 'www.example.com'], // simple host
|
||
|
['[::1]', '[::1]'], // IPv6 Host
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('hostProvider')]
|
||
|
public function testGetHost(string $host, string $expected): void {
|
||
|
$uri = $this->uri->withHost($host);
|
||
|
$this->assertInstanceOf(UriInterface::class, $uri);
|
||
|
$this->assertSame($expected, $uri->getHost());
|
||
|
$this->assertInstanceOf(HttpUri::class, $uri);
|
||
|
$this->assertSame($expected, $uri->host);
|
||
|
}
|
||
|
|
||
|
public static function portProvider(): array {
|
||
|
return [
|
||
|
['http://www.example.com', 443, 443], // non standard port for http
|
||
|
['http://www.example.com', null, null], // remove port
|
||
|
['//www.example.com', 80, 80], // standard port on schemeless http url
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('portProvider')]
|
||
|
public function testGetPort(string $uri, ?int $port, ?int $expected): void {
|
||
|
$uri = HttpUri::createUri($uri)->withPort($port);
|
||
|
$this->assertInstanceOf(UriInterface::class, $uri);
|
||
|
$this->assertSame($expected, $uri->getPort());
|
||
|
$this->assertInstanceOf(HttpUri::class, $uri);
|
||
|
$this->assertSame($expected, $uri->port);
|
||
|
}
|
||
|
|
||
|
public static function authorityProvider(): array {
|
||
|
return [
|
||
|
[
|
||
|
'scheme' => 'http',
|
||
|
'user' => 'FreakyFurball',
|
||
|
'pass' => 'Express1',
|
||
|
'host' => 'example.com',
|
||
|
'port' => 443,
|
||
|
'expected' => 'FreakyFurball:Express1@example.com:443',
|
||
|
],
|
||
|
[
|
||
|
'scheme' => 'http',
|
||
|
'user' => 'Reemo',
|
||
|
'pass' => 'BuddyMan5',
|
||
|
'host' => 'example.com',
|
||
|
'port' => null,
|
||
|
'expected' => 'Reemo:BuddyMan5@example.com',
|
||
|
],
|
||
|
[
|
||
|
'scheme' => 'http',
|
||
|
'user' => 'Unko',
|
||
|
'pass' => 'SoapSoapSoapSoapSoap',
|
||
|
'host' => 'example.com',
|
||
|
'port' => 80,
|
||
|
'expected' => 'Unko:SoapSoapSoapSoapSoap@example.com:80',
|
||
|
],
|
||
|
[
|
||
|
'scheme' => 'http',
|
||
|
'user' => 'flash',
|
||
|
'pass' => '',
|
||
|
'host' => 'example.com',
|
||
|
'port' => null,
|
||
|
'expected' => 'flash:@example.com',
|
||
|
],
|
||
|
[
|
||
|
'scheme' => 'http',
|
||
|
'user' => 'Satori',
|
||
|
'pass' => null,
|
||
|
'host' => 'example.com',
|
||
|
'port' => null,
|
||
|
'expected' => 'Satori@example.com',
|
||
|
],
|
||
|
[
|
||
|
'scheme' => 'http',
|
||
|
'user' => '',
|
||
|
'pass' => '',
|
||
|
'host' => 'example.com',
|
||
|
'port' => null,
|
||
|
'expected' => 'example.com',
|
||
|
],
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('authorityProvider')]
|
||
|
public function testGetAuthority(string $scheme, string $user, ?string $pass, string $host, ?int $port, string $expected): void {
|
||
|
$uri = HttpUri::createUri()->withHost($host)->withScheme($scheme)->withUserInfo($user, $pass)->withPort($port);
|
||
|
$this->assertInstanceOf(UriInterface::class, $uri);
|
||
|
$this->assertSame($expected, $uri->getAuthority());
|
||
|
$this->assertInstanceOf(HttpUri::class, $uri);
|
||
|
$this->assertSame($expected, $uri->authority);
|
||
|
}
|
||
|
|
||
|
public static function queryProvider(): array {
|
||
|
return [
|
||
|
['foo.bar=%7evalue', 'foo.bar=%7evalue'], // normalized query
|
||
|
['', ''], // empty query
|
||
|
['foo.bar=1&foo.bar=1', 'foo.bar=1&foo.bar=1'], // same param query
|
||
|
['?foo=', '?foo='], // same param query, may include ?
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('queryProvider')]
|
||
|
public function testGetQuery(string $query, string $expected): void {
|
||
|
$uri = $this->uri->withQuery($query);
|
||
|
$this->assertInstanceOf(UriInterface::class, $uri);
|
||
|
$this->assertSame($expected, $uri->getQuery());
|
||
|
$this->assertInstanceOf(HttpUri::class, $uri);
|
||
|
$this->assertSame($expected, $uri->query);
|
||
|
}
|
||
|
|
||
|
public static function fragmentProvider(): array {
|
||
|
return [
|
||
|
['fragment', 'fragment'], // all components
|
||
|
['azAZ0-9/?-._~!$&\'()*+,;=:@', 'azAZ0-9/?-._~!$&\'()*+,;=:@'], // non-encodable
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('fragmentProvider')]
|
||
|
public function testGetFragment(string $fragment, string $expected): void {
|
||
|
$uri = $this->uri->withFragment($fragment);
|
||
|
$this->assertInstanceOf(UriInterface::class, $uri);
|
||
|
$this->assertSame($expected, $uri->getFragment());
|
||
|
$this->assertInstanceOf(HttpUri::class, $uri);
|
||
|
$this->assertSame($expected, $uri->fragment);
|
||
|
}
|
||
|
|
||
|
public static function stringProvider(): array {
|
||
|
return [
|
||
|
[
|
||
|
'scheme' => 'HtTps',
|
||
|
'user' => 'FreakyFurball',
|
||
|
'pass' => 'Express1',
|
||
|
'host' => 'MeWow.eXaMpLe.CoM',
|
||
|
'port' => 443,
|
||
|
'path' => '/%7ejanedoe/%a1/index.php',
|
||
|
'query' => 'foo.bar=%7evalue',
|
||
|
'fragment' => 'fragment',
|
||
|
'expected' => 'https://FreakyFurball:Express1@mewow.example.com:443/%7ejanedoe/%a1/index.php?foo.bar=%7evalue#fragment'
|
||
|
],
|
||
|
[
|
||
|
'scheme' => '',
|
||
|
'user' => '',
|
||
|
'pass' => '',
|
||
|
'host' => 'www.example.com',
|
||
|
'port' => 443,
|
||
|
'path' => '/foo/bar',
|
||
|
'query' => 'param=value',
|
||
|
'fragment' => 'fragment',
|
||
|
'expected' => '//www.example.com:443/foo/bar?param=value#fragment',
|
||
|
],
|
||
|
[
|
||
|
'scheme' => '',
|
||
|
'user' => '',
|
||
|
'pass' => '',
|
||
|
'host' => '',
|
||
|
'port' => null,
|
||
|
'path' => 'foo/bar',
|
||
|
'query' => '',
|
||
|
'fragment' => '',
|
||
|
'expected' => 'foo/bar',
|
||
|
],
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('stringProvider')]
|
||
|
public function testToString(
|
||
|
string $scheme,
|
||
|
string $user,
|
||
|
string $pass,
|
||
|
string $host,
|
||
|
?int $port,
|
||
|
string $path,
|
||
|
string $query,
|
||
|
string $fragment,
|
||
|
string $expected
|
||
|
): void {
|
||
|
$uri = HttpUri::createUri()->withHost($host)->withScheme($scheme)
|
||
|
->withUserInfo($user, $pass)->withPort($port)
|
||
|
->withPath($path)->withQuery($query)->withFragment($fragment);
|
||
|
$this->assertInstanceOf(UriInterface::class, $uri);
|
||
|
$this->assertInstanceOf(Stringable::class, $uri); // HttpUri also implements this
|
||
|
$this->assertSame($expected, (string)$uri);
|
||
|
}
|
||
|
|
||
|
public function testRemoveScheme(): void {
|
||
|
$this->assertSame(
|
||
|
'//example.com/this/is/a/path',
|
||
|
(string)HttpUri::createUri('http://example.com/this/is/a/path')->withScheme('')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public function testRemoveAuthority(): void {
|
||
|
$uri = HttpUri::createUri('http://user:login@example.com:82/path?q=v#doc')
|
||
|
->withScheme('')->withUserInfo('')->withPort(null)->withHost('');
|
||
|
|
||
|
$this->assertSame('/path?q=v#doc', (string)$uri);
|
||
|
}
|
||
|
|
||
|
public function testRemoveUserInfo(): void {
|
||
|
$this->assertSame(
|
||
|
'http://example.com/this/is/a/path',
|
||
|
(string)HttpUri::createUri('http://user:pass@example.com/this/is/a/path')->withUserInfo('')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public function testRemovePort(): void {
|
||
|
$this->assertSame(
|
||
|
'http://example.com/this/is/a/path',
|
||
|
(string)HttpUri::createUri('http://example.com:9001/this/is/a/path')->withPort(null)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public function testRemovePath(): void {
|
||
|
$uri = 'http://example.com';
|
||
|
$this->assertSame($uri, (string)HttpUri::createUri($uri . '/this/is/a/path')->withPath(''));
|
||
|
}
|
||
|
|
||
|
public function testRemoveQuery(): void {
|
||
|
$uri = 'http://example.com/this/is/a/path';
|
||
|
$this->assertSame($uri, (string)HttpUri::createUri($uri . '?name=value')->withQuery(''));
|
||
|
}
|
||
|
|
||
|
public function testRemoveFragment(): void {
|
||
|
$uri = 'http://example.com/this/is/a/path';
|
||
|
$this->assertSame($uri, (string)HttpUri::createUri($uri . '#soap')->withFragment(''));
|
||
|
}
|
||
|
|
||
|
public static function withSchemeFailProvider(): array {
|
||
|
return [
|
||
|
['un,acceptable'], // unacceptable char
|
||
|
['123'], // number string
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('withSchemeFailProvider')]
|
||
|
public function testWithSchemeFail(string $scheme): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
$this->uri->withScheme($scheme);
|
||
|
}
|
||
|
|
||
|
public static function withUserInfoFailProvider(): array {
|
||
|
return [
|
||
|
['mew:ow', 'Soap'],
|
||
|
['be@ns', 'Soup'],
|
||
|
['Meow', 'ok@y'],
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('withUserInfoFailProvider')]
|
||
|
public function testWithUserInfoFail(string $user, ?string $pass): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
$this->uri->withUserInfo($user, $pass);
|
||
|
}
|
||
|
|
||
|
public static function withHostFailProvider(): array {
|
||
|
return [
|
||
|
['.example.com'], // dot in front
|
||
|
['host.com-'], // hyphen suffix
|
||
|
['.......'], // multiple dot
|
||
|
['.'], // one dot
|
||
|
['tot. .coucou.com'], // empty label
|
||
|
['re view'], // space in the label
|
||
|
['_bad.host.com'], // underscore in label
|
||
|
[implode('', array_fill(0, 12, 'banana')).'.secure.example.com'], // label too long
|
||
|
[implode('.', array_fill(0, 128, 'a'))], // too many labels
|
||
|
['[127.0.0.1]'], // Invalid IPv4 format
|
||
|
['[[::1]]'], // Invalid IPv6 format
|
||
|
['[::1'], // Invalid IPv6 format 2
|
||
|
['example. com'], // space character in starting label
|
||
|
["examp\0le.com"], // invalid character in host label
|
||
|
['[127.2.0.1%253]'], // invalid IP with scope
|
||
|
['ab23::1234%251'], // invalid scope IPv6
|
||
|
['fe80::1234%25?@'], // invalid scope ID
|
||
|
['fe80::1234%25€'], // invalid scope ID with utf8 character
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('withHostFailProvider')]
|
||
|
public function testWithHostFail(string $host): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
$this->uri->withHost($host);
|
||
|
}
|
||
|
|
||
|
public function testWithPathFailWithInvalidPathRelativeToAuthority(): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
HttpUri::createUri('https://example.com')->withPath('me/ow');
|
||
|
}
|
||
|
|
||
|
public function testWithPathFailWithInvalidChars(): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
HttpUri::createUri('https://example.com')->withPath('/?');
|
||
|
}
|
||
|
|
||
|
public function testWithQueryFailWithInvalidChars(): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
HttpUri::createUri('https://example.com')->withQuery('#');
|
||
|
}
|
||
|
|
||
|
public function testWithPortFailWithTooLowPort(): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
HttpUri::createUri('https://example.com')->withPort(0);
|
||
|
}
|
||
|
|
||
|
public function testWithPortFailWithTooHighPort(): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
HttpUri::createUri('https://example.com')->withPort(0x10000);
|
||
|
}
|
||
|
|
||
|
public function testWithHostFailWithInvalidHost(): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
HttpUri::createUri('https://example.com')->withHost('?');
|
||
|
}
|
||
|
|
||
|
public static function invalidUriProvider(): array {
|
||
|
return [
|
||
|
['https://user@:443'],
|
||
|
[':'],
|
||
|
];
|
||
|
}
|
||
|
|
||
|
#[DataProvider('invalidUriProvider')]
|
||
|
public function testCreateUriWithInvalidUri(string $uri): void {
|
||
|
$this->expectException(InvalidArgumentException::class);
|
||
|
HttpUri::createUri($uri);
|
||
|
}
|
||
|
|
||
|
public function testZeroValues(): void {
|
||
|
$expected = '//0:0@host/0?0#0';
|
||
|
$this->assertSame($expected, (string)HttpUri::createUri($expected));
|
||
|
}
|
||
|
|
||
|
public function testPathDetection(): void {
|
||
|
$expected = 'foo/bar:';
|
||
|
$this->assertSame($expected, HttpUri::createUri($expected)->path);
|
||
|
}
|
||
|
}
|