index/tests/HttpUriTest.php

399 lines
15 KiB
PHP
Raw Normal View History

2025-02-28 02:20:30 +00:00
<?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);
}
}