- 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.
267 lines
9.7 KiB
PHP
267 lines
9.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Http\Url\Rfc3986Url;
|
|
use App\Framework\Http\Url\UrlFactory;
|
|
use App\Framework\Http\Url\UrlSpec;
|
|
use App\Framework\Http\Url\UrlUseCase;
|
|
use App\Framework\Http\Url\WhatwgUrl;
|
|
|
|
describe('UrlFactory', function () {
|
|
describe('automatic spec selection', function () {
|
|
it('selects WHATWG for http/https URLs', function () {
|
|
$url = UrlFactory::parse('https://example.com/path');
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
expect($url->getSpec())->toBe(UrlSpec::WHATWG);
|
|
});
|
|
|
|
it('selects RFC3986 for non-http schemes', function () {
|
|
$url = UrlFactory::parse('ftp://example.com/file');
|
|
|
|
expect($url)->toBeInstanceOf(Rfc3986Url::class);
|
|
expect($url->getSpec())->toBe(UrlSpec::RFC3986);
|
|
});
|
|
|
|
it('selects WHATWG for file:// URLs', function () {
|
|
$url = UrlFactory::parse('file:///path/to/file');
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
|
|
it('selects WHATWG for WebSocket URLs', function () {
|
|
$url = UrlFactory::parse('ws://example.com/socket');
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
});
|
|
|
|
describe('use case factory methods', function () {
|
|
it('creates RFC3986 URL for API client', function () {
|
|
$url = UrlFactory::forApiClient('https://api.example.com/users');
|
|
|
|
expect($url)->toBeInstanceOf(Rfc3986Url::class);
|
|
expect($url->getHost())->toBe('api.example.com');
|
|
expect($url->getPath())->toBe('/users');
|
|
});
|
|
|
|
it('creates RFC3986 URL for cURL request', function () {
|
|
$url = UrlFactory::forCurlRequest('https://example.com/endpoint');
|
|
|
|
expect($url)->toBeInstanceOf(Rfc3986Url::class);
|
|
});
|
|
|
|
it('creates RFC3986 URL for signature generation', function () {
|
|
$url = UrlFactory::forSignature('https://api.example.com/resource');
|
|
|
|
expect($url)->toBeInstanceOf(Rfc3986Url::class);
|
|
});
|
|
|
|
it('creates RFC3986 URL for canonical URLs', function () {
|
|
$url = UrlFactory::forCanonical('https://example.com/page');
|
|
|
|
expect($url)->toBeInstanceOf(Rfc3986Url::class);
|
|
});
|
|
|
|
it('creates WHATWG URL for browser redirect', function () {
|
|
$url = UrlFactory::forBrowserRedirect('https://example.com/redirect');
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
|
|
it('creates WHATWG URL for deep link', function () {
|
|
$url = UrlFactory::forDeepLink('myapp://open/profile');
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
|
|
it('creates WHATWG URL for form action', function () {
|
|
$url = UrlFactory::forFormAction('https://example.com/submit');
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
|
|
it('creates WHATWG URL for client-side JavaScript', function () {
|
|
$url = UrlFactory::forClientSide('https://example.com/api/data');
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
});
|
|
|
|
describe('use case automatic selection', function () {
|
|
it('creates RFC3986 for API_CLIENT use case', function () {
|
|
$url = UrlFactory::forUseCase(
|
|
UrlUseCase::API_CLIENT,
|
|
'https://api.example.com/users'
|
|
);
|
|
|
|
expect($url)->toBeInstanceOf(Rfc3986Url::class);
|
|
});
|
|
|
|
it('creates RFC3986 for CURL_REQUEST use case', function () {
|
|
$url = UrlFactory::forUseCase(
|
|
UrlUseCase::CURL_REQUEST,
|
|
'https://example.com/file'
|
|
);
|
|
|
|
expect($url)->toBeInstanceOf(Rfc3986Url::class);
|
|
});
|
|
|
|
it('creates RFC3986 for SIGNATURE_GENERATION use case', function () {
|
|
$url = UrlFactory::forUseCase(
|
|
UrlUseCase::SIGNATURE_GENERATION,
|
|
'https://api.example.com/sign'
|
|
);
|
|
|
|
expect($url)->toBeInstanceOf(Rfc3986Url::class);
|
|
});
|
|
|
|
it('creates RFC3986 for CANONICAL_URL use case', function () {
|
|
$url = UrlFactory::forUseCase(
|
|
UrlUseCase::CANONICAL_URL,
|
|
'https://example.com/page'
|
|
);
|
|
|
|
expect($url)->toBeInstanceOf(Rfc3986Url::class);
|
|
});
|
|
|
|
it('creates WHATWG for BROWSER_REDIRECT use case', function () {
|
|
$url = UrlFactory::forUseCase(
|
|
UrlUseCase::BROWSER_REDIRECT,
|
|
'https://example.com/redirect'
|
|
);
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
|
|
it('creates WHATWG for DEEP_LINK use case', function () {
|
|
$url = UrlFactory::forUseCase(
|
|
UrlUseCase::DEEP_LINK,
|
|
'myapp://open'
|
|
);
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
|
|
it('creates WHATWG for HTML_FORM_ACTION use case', function () {
|
|
$url = UrlFactory::forUseCase(
|
|
UrlUseCase::HTML_FORM_ACTION,
|
|
'https://example.com/submit'
|
|
);
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
|
|
it('creates WHATWG for CLIENT_SIDE_URL use case', function () {
|
|
$url = UrlFactory::forUseCase(
|
|
UrlUseCase::CLIENT_SIDE_URL,
|
|
'https://example.com/api'
|
|
);
|
|
|
|
expect($url)->toBeInstanceOf(WhatwgUrl::class);
|
|
});
|
|
});
|
|
|
|
describe('spec conversion', function () {
|
|
it('converts RFC3986 to WHATWG', function () {
|
|
$rfc = UrlFactory::forApiClient('https://example.com/path');
|
|
$whatwg = UrlFactory::convert($rfc, UrlSpec::WHATWG);
|
|
|
|
expect($whatwg)->toBeInstanceOf(WhatwgUrl::class);
|
|
expect($whatwg->getHost())->toBe('example.com');
|
|
expect($whatwg->getPath())->toBe('/path');
|
|
});
|
|
|
|
it('converts WHATWG to RFC3986', function () {
|
|
$whatwg = UrlFactory::forBrowserRedirect('https://example.com/path');
|
|
$rfc = UrlFactory::convert($whatwg, UrlSpec::RFC3986);
|
|
|
|
expect($rfc)->toBeInstanceOf(Rfc3986Url::class);
|
|
expect($rfc->getHost())->toBe('example.com');
|
|
expect($rfc->getPath())->toBe('/path');
|
|
});
|
|
|
|
it('returns same instance if already correct spec', function () {
|
|
$rfc = UrlFactory::forApiClient('https://example.com');
|
|
$converted = UrlFactory::convert($rfc, UrlSpec::RFC3986);
|
|
|
|
expect($converted)->toBe($rfc);
|
|
});
|
|
});
|
|
|
|
describe('integration scenarios', function () {
|
|
it('creates API client URL with authentication', function () {
|
|
$url = UrlFactory::forApiClient('https://api.example.com/users');
|
|
$withAuth = $url->withUserInfo('api_key', 'secret');
|
|
|
|
expect($withAuth->getUserInfo())->toBe('api_key:secret');
|
|
expect($withAuth->toString())->toContain('api_key:secret');
|
|
});
|
|
|
|
it('creates redirect URL with query parameters', function () {
|
|
$url = UrlFactory::forBrowserRedirect('https://example.com/login');
|
|
$withParams = $url->withQuery('return_url=/dashboard&status=success');
|
|
|
|
expect($withParams->getQuery())->toContain('return_url');
|
|
});
|
|
|
|
it('creates canonical URL without query and fragment', function () {
|
|
$url = UrlFactory::forCanonical('https://example.com/page?utm=123#section');
|
|
$canonical = $url->withQuery('')->withFragment('');
|
|
|
|
expect($canonical->toString())->toBe('https://example.com/page');
|
|
});
|
|
|
|
it('creates signature-safe URL', function () {
|
|
$url = UrlFactory::forSignature('https://api.example.com/resource');
|
|
$withSig = $url->withQuery('timestamp=1234567890&signature=abc123');
|
|
|
|
$urlString = $withSig->toString();
|
|
|
|
expect($urlString)->toContain('timestamp=1234567890');
|
|
expect($urlString)->toContain('signature=abc123');
|
|
});
|
|
|
|
it('creates deep link with custom data', function () {
|
|
$url = UrlFactory::forDeepLink('myapp://open/profile');
|
|
$withData = $url->withQuery('user_id=123&action=view');
|
|
|
|
expect($withData->getScheme())->toBe('myapp');
|
|
expect($withData->getQuery())->toContain('user_id=123');
|
|
});
|
|
});
|
|
|
|
describe('real-world scenarios', function () {
|
|
it('handles OAuth URL construction', function () {
|
|
$authUrl = UrlFactory::forApiClient('https://oauth.example.com/authorize');
|
|
$withParams = $authUrl->withQuery('client_id=123&redirect_uri=https://app.com&scope=read');
|
|
|
|
expect($withParams->getQuery())->toContain('client_id=123');
|
|
expect($withParams->getQuery())->toContain('redirect_uri');
|
|
});
|
|
|
|
it('handles REST API pagination', function () {
|
|
$apiUrl = UrlFactory::forApiClient('https://api.example.com/users');
|
|
$page2 = $apiUrl->withQuery('page=2&per_page=50');
|
|
|
|
expect($page2->getQuery())->toBe('page=2&per_page=50');
|
|
});
|
|
|
|
it('handles mobile deep link with fallback', function () {
|
|
$deepLink = UrlFactory::forDeepLink('myapp://open/article/123');
|
|
$fallback = UrlFactory::forBrowserRedirect('https://example.com/article/123');
|
|
|
|
expect($deepLink->getScheme())->toBe('myapp');
|
|
expect($fallback->getScheme())->toBe('https');
|
|
});
|
|
|
|
it('handles form submission with CSRF token', function () {
|
|
$formAction = UrlFactory::forFormAction('https://example.com/submit');
|
|
$withToken = $formAction->withQuery('csrf_token=abc123');
|
|
|
|
expect($withToken->getQuery())->toContain('csrf_token');
|
|
});
|
|
});
|
|
});
|