- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
264 lines
9.5 KiB
PHP
264 lines
9.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Http\Url\UrlSpec;
|
|
use App\Framework\Http\Url\WhatwgUrl;
|
|
|
|
describe('WhatwgUrl', function () {
|
|
describe('parsing', function () {
|
|
it('parses basic HTTP URLs', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/path');
|
|
|
|
expect($url->getScheme())->toBe('https');
|
|
expect($url->getHost())->toBe('example.com');
|
|
expect($url->getPath())->toBe('/path');
|
|
});
|
|
|
|
it('parses URLs with all components', function () {
|
|
$url = WhatwgUrl::parse('https://user:pass@example.com:8080/path?query=1#fragment');
|
|
|
|
expect($url->getScheme())->toBe('https');
|
|
expect($url->getUserInfo())->toBe('user:pass');
|
|
expect($url->getHost())->toBe('example.com');
|
|
expect($url->getPort())->toBe(8080);
|
|
expect($url->getPath())->toBe('/path');
|
|
expect($url->getQuery())->toBe('query=1');
|
|
expect($url->getFragment())->toBe('fragment');
|
|
});
|
|
|
|
it('handles default ports correctly', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/path');
|
|
|
|
expect($url->getPort())->toBeNull();
|
|
});
|
|
|
|
it('parses relative URLs with base', function () {
|
|
$base = WhatwgUrl::parse('https://example.com/base/path');
|
|
$url = WhatwgUrl::parse('../other', $base);
|
|
|
|
expect($url->getPath())->toContain('other');
|
|
});
|
|
|
|
it('normalizes URLs according to WHATWG spec', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/./path/../other');
|
|
|
|
// WHATWG normalizes . and .. in paths
|
|
expect($url->getPath())->toBe('/other');
|
|
});
|
|
});
|
|
|
|
describe('spec compliance', function () {
|
|
it('reports WHATWG spec', function () {
|
|
$url = WhatwgUrl::parse('https://example.com');
|
|
|
|
expect($url->getSpec())->toBe(UrlSpec::WHATWG);
|
|
expect($url->getSpec()->isWhatwg())->toBeTrue();
|
|
expect($url->getSpec()->isRfc3986())->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('immutable withers', function () {
|
|
it('returns new instance with different scheme', function () {
|
|
$original = WhatwgUrl::parse('https://example.com');
|
|
$modified = $original->withScheme('http');
|
|
|
|
expect($original->getScheme())->toBe('https');
|
|
expect($modified->getScheme())->toBe('http');
|
|
expect(spl_object_id($original))->notToBe(spl_object_id($modified));
|
|
});
|
|
|
|
it('returns new instance with different host', function () {
|
|
$original = WhatwgUrl::parse('https://example.com');
|
|
$modified = $original->withHost('app.example.com');
|
|
|
|
expect($original->getHost())->toBe('example.com');
|
|
expect($modified->getHost())->toBe('app.example.com');
|
|
});
|
|
|
|
it('returns new instance with different port', function () {
|
|
$original = WhatwgUrl::parse('https://example.com');
|
|
$modified = $original->withPort(8080);
|
|
|
|
expect($original->getPort())->toBeNull();
|
|
expect($modified->getPort())->toBe(8080);
|
|
});
|
|
|
|
it('returns new instance with different path', function () {
|
|
$original = WhatwgUrl::parse('https://example.com/old');
|
|
$modified = $original->withPath('/new');
|
|
|
|
expect($original->getPath())->toBe('/old');
|
|
expect($modified->getPath())->toBe('/new');
|
|
});
|
|
|
|
it('returns new instance with different query', function () {
|
|
$original = WhatwgUrl::parse('https://example.com?old=1');
|
|
$modified = $original->withQuery('new=2');
|
|
|
|
expect($original->getQuery())->toBe('old=1');
|
|
expect($modified->getQuery())->toBe('new=2');
|
|
});
|
|
|
|
it('returns new instance with different fragment', function () {
|
|
$original = WhatwgUrl::parse('https://example.com#old');
|
|
$modified = $original->withFragment('new');
|
|
|
|
expect($original->getFragment())->toBe('old');
|
|
expect($modified->getFragment())->toBe('new');
|
|
});
|
|
|
|
it('returns new instance with user info', function () {
|
|
$original = WhatwgUrl::parse('https://example.com');
|
|
$modified = $original->withUserInfo('user', 'pass');
|
|
|
|
expect($original->getUserInfo())->toBe('');
|
|
expect($modified->getUserInfo())->toBe('user:pass');
|
|
});
|
|
|
|
it('handles user info without password', function () {
|
|
$url = WhatwgUrl::parse('https://example.com');
|
|
$modified = $url->withUserInfo('user');
|
|
|
|
expect($modified->getUserInfo())->toBe('user');
|
|
});
|
|
});
|
|
|
|
describe('serialization', function () {
|
|
it('converts to string', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/path?query=1#fragment');
|
|
|
|
expect($url->toString())->toBe('https://example.com/path?query=1#fragment');
|
|
expect((string) $url)->toBe('https://example.com/path?query=1#fragment');
|
|
});
|
|
|
|
it('handles IDNA domains (Punycode)', function () {
|
|
$url = WhatwgUrl::parse('https://例え.jp/path');
|
|
$str = $url->toString();
|
|
|
|
// WHATWG automatically Punycode-encodes
|
|
expect($str)->toContain('xn--');
|
|
});
|
|
|
|
it('toAsciiString returns same as toString for WHATWG', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/path');
|
|
|
|
expect($url->toAsciiString())->toBe($url->toString());
|
|
});
|
|
});
|
|
|
|
describe('URL resolution', function () {
|
|
it('resolves relative URLs', function () {
|
|
$base = WhatwgUrl::parse('https://example.com/base/path');
|
|
$resolved = $base->resolve('relative');
|
|
|
|
expect($resolved->getHost())->toBe('example.com');
|
|
expect($resolved->getPath())->toContain('relative');
|
|
});
|
|
|
|
it('resolves absolute URLs', function () {
|
|
$base = WhatwgUrl::parse('https://example.com/base');
|
|
$resolved = $base->resolve('https://other.com/path');
|
|
|
|
expect($resolved->getHost())->toBe('other.com');
|
|
expect($resolved->getPath())->toBe('/path');
|
|
});
|
|
|
|
it('resolves with path normalization', function () {
|
|
$base = WhatwgUrl::parse('https://example.com/base');
|
|
$resolved = $base->resolve('./path/../other');
|
|
|
|
expect($resolved->getPath())->toBe('/other');
|
|
});
|
|
});
|
|
|
|
describe('URL comparison', function () {
|
|
it('compares URLs for equality', function () {
|
|
$url1 = WhatwgUrl::parse('https://example.com/path');
|
|
$url2 = WhatwgUrl::parse('https://example.com/path');
|
|
|
|
expect($url1->equals($url2))->toBeTrue();
|
|
});
|
|
|
|
it('compares URLs ignoring fragments by default', function () {
|
|
$url1 = WhatwgUrl::parse('https://example.com/path#frag1');
|
|
$url2 = WhatwgUrl::parse('https://example.com/path#frag2');
|
|
|
|
expect($url1->equals($url2))->toBeTrue();
|
|
expect($url1->equals($url2, includeFragment: true))->toBeFalse();
|
|
});
|
|
|
|
it('detects different URLs', function () {
|
|
$url1 = WhatwgUrl::parse('https://example.com/path1');
|
|
$url2 = WhatwgUrl::parse('https://example.com/path2');
|
|
|
|
expect($url1->equals($url2))->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('edge cases', function () {
|
|
it('handles trailing slashes', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/path/');
|
|
|
|
expect($url->getPath())->toBe('/path/');
|
|
});
|
|
|
|
it('handles empty path', function () {
|
|
$url = WhatwgUrl::parse('https://example.com');
|
|
|
|
expect($url->getPath())->toBe('/');
|
|
});
|
|
|
|
it('handles empty query', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/path');
|
|
|
|
expect($url->getQuery())->toBe('');
|
|
});
|
|
|
|
it('handles empty fragment', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/path');
|
|
|
|
expect($url->getFragment())->toBe('');
|
|
});
|
|
|
|
it('removes port with null', function () {
|
|
$url = WhatwgUrl::parse('https://example.com:8080/path');
|
|
$modified = $url->withPort(null);
|
|
|
|
expect($modified->getPort())->toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('browser redirect use cases', function () {
|
|
it('handles query parameters for redirects', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/redirect');
|
|
$withParams = $url->withQuery('return_url=https://other.com&status=success');
|
|
|
|
expect($withParams->getQuery())->toContain('return_url');
|
|
expect($withParams->getQuery())->toContain('status=success');
|
|
});
|
|
|
|
it('handles fragment identifiers', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/page#section');
|
|
|
|
expect($url->getFragment())->toBe('section');
|
|
});
|
|
|
|
it('normalizes paths for browser compatibility', function () {
|
|
$url = WhatwgUrl::parse('https://example.com/./path/../other');
|
|
|
|
expect($url->getPath())->toBe('/other');
|
|
});
|
|
});
|
|
|
|
describe('deep link use cases', function () {
|
|
it('handles custom schemes', function () {
|
|
$url = WhatwgUrl::parse('myapp://open/profile?user_id=123');
|
|
|
|
expect($url->getScheme())->toBe('myapp');
|
|
expect($url->getPath())->toContain('profile');
|
|
expect($url->getQuery())->toContain('user_id=123');
|
|
});
|
|
});
|
|
});
|