Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,424 @@
<?php
declare(strict_types=1);
use App\Framework\Attributes\Route;
use App\Framework\Core\RouteCompiler;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\Discovery\ValueObjects\AttributeTarget;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\ServerEnvironment;
use App\Framework\Router\HttpRouter;
use App\Framework\Router\RouteMatchSuccess;
it('compiles routes with exact subdomain patterns', function () {
$compiler = new RouteCompiler();
// Create a discovered route with exact subdomain
$discoveredRoute = new DiscoveredAttribute(
className: ClassName::create('TestController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('apiEndpoint'),
arguments: [
'path' => '/api/test',
'method' => Method::GET,
'subdomain' => 'api',
],
additionalData: ['parameters' => []]
);
$compiled = $compiler->compile($discoveredRoute);
expect($compiled)->toHaveKey('GET')
->and($compiled['GET'])->toHaveKey('exact:api')
->and($compiled['GET']['exact:api']['static'])->toHaveKey('/api/test');
});
it('compiles routes with wildcard subdomain patterns', function () {
$compiler = new RouteCompiler();
$discoveredRoute = new DiscoveredAttribute(
className: ClassName::create('TestController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('wildcardEndpoint'),
arguments: [
'path' => '/wildcard',
'method' => Method::GET,
'subdomain' => '*.app',
],
additionalData: ['parameters' => []]
);
$compiled = $compiler->compile($discoveredRoute);
expect($compiled)->toHaveKey('GET')
->and($compiled['GET'])->toHaveKey('wildcard:*.app')
->and($compiled['GET']['wildcard:*.app']['static'])->toHaveKey('/wildcard');
});
it('compiles routes with multiple subdomain patterns', function () {
$compiler = new RouteCompiler();
$discoveredRoute = new DiscoveredAttribute(
className: ClassName::create('TestController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('multiSubdomain'),
arguments: [
'path' => '/multi',
'method' => Method::GET,
'subdomain' => ['api', 'admin'],
],
additionalData: ['parameters' => []]
);
$compiled = $compiler->compile($discoveredRoute);
expect($compiled['GET'])->toHaveKey('exact:api')
->and($compiled['GET'])->toHaveKey('exact:admin')
->and($compiled['GET']['exact:api']['static'])->toHaveKey('/multi')
->and($compiled['GET']['exact:admin']['static'])->toHaveKey('/multi');
});
it('falls back to default routes when no subdomain specified', function () {
$compiler = new RouteCompiler();
$discoveredRoute = new DiscoveredAttribute(
className: ClassName::create('TestController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('defaultRoute'),
arguments: [
'path' => '/default',
'method' => Method::GET,
'subdomain' => [],
],
additionalData: ['parameters' => []]
);
$compiled = $compiler->compile($discoveredRoute);
expect($compiled)->toHaveKey('GET')
->and($compiled['GET'])->toHaveKey('default')
->and($compiled['GET']['default']['static'])->toHaveKey('/default');
});
it('router matches exact subdomain routes correctly', function () {
$compiler = new RouteCompiler();
// Create routes for different subdomains
$apiRoute = new DiscoveredAttribute(
className: ClassName::create('ApiController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('index'),
arguments: [
'path' => '/test',
'method' => Method::GET,
'subdomain' => 'api',
],
additionalData: ['parameters' => []]
);
$defaultRoute = new DiscoveredAttribute(
className: ClassName::create('WebController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('index'),
arguments: [
'path' => '/test',
'method' => Method::GET,
'subdomain' => [],
],
additionalData: ['parameters' => []]
);
$compiledRoutes = $compiler->compileOptimized($apiRoute, $defaultRoute);
$router = new HttpRouter($compiledRoutes);
// Test API subdomain request
$apiRequest = new HttpRequest(
method: Method::GET,
path: '/test',
headers: new Headers(['Host' => 'api.example.com']),
server: new ServerEnvironment(['HTTP_HOST' => 'api.example.com'])
);
$apiContext = $router->match($apiRequest);
expect($apiContext->match)->toBeInstanceOf(RouteMatchSuccess::class);
if ($apiContext->match instanceof RouteMatchSuccess) {
expect($apiContext->match->route->controller)->toBe('ApiController');
}
// Test default domain request
$webRequest = new HttpRequest(
method: Method::GET,
path: '/test',
headers: new Headers(['Host' => 'example.com']),
server: new ServerEnvironment(['HTTP_HOST' => 'example.com'])
);
$webContext = $router->match($webRequest);
expect($webContext->match)->toBeInstanceOf(RouteMatchSuccess::class);
if ($webContext->match instanceof RouteMatchSuccess) {
expect($webContext->match->route->controller)->toBe('WebController');
}
});
it('router matches wildcard subdomain patterns', function () {
$compiler = new RouteCompiler();
$wildcardRoute = new DiscoveredAttribute(
className: ClassName::create('TenantController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('dashboard'),
arguments: [
'path' => '/dashboard',
'method' => Method::GET,
'subdomain' => '*.app',
],
additionalData: ['parameters' => []]
);
$compiledRoutes = $compiler->compileOptimized($wildcardRoute);
$router = new HttpRouter($compiledRoutes);
// Test wildcard match
$tenantRequest = new HttpRequest(
method: Method::GET,
path: '/dashboard',
headers: new Headers(['Host' => 'tenant1.app.example.com']),
server: new ServerEnvironment(['HTTP_HOST' => 'tenant1.app.example.com'])
);
$context = $router->match($tenantRequest);
expect($context->match)->toBeInstanceOf(RouteMatchSuccess::class);
if ($context->match instanceof RouteMatchSuccess) {
expect($context->match->route->controller)->toBe('TenantController');
}
});
it('router prioritizes exact subdomain over wildcard patterns', function () {
$compiler = new RouteCompiler();
$exactRoute = new DiscoveredAttribute(
className: ClassName::create('ExactController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('special'),
arguments: [
'path' => '/special',
'method' => Method::GET,
'subdomain' => 'admin',
],
additionalData: ['parameters' => []]
);
$wildcardRoute = new DiscoveredAttribute(
className: ClassName::create('WildcardController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('general'),
arguments: [
'path' => '/special',
'method' => Method::GET,
'subdomain' => '*',
],
additionalData: ['parameters' => []]
);
$compiledRoutes = $compiler->compileOptimized($exactRoute, $wildcardRoute);
$router = new HttpRouter($compiledRoutes);
// Test that exact match takes precedence
$request = new HttpRequest(
method: Method::GET,
path: '/special',
headers: new Headers(['Host' => 'admin.example.com']),
server: new ServerEnvironment(['HTTP_HOST' => 'admin.example.com'])
);
$context = $router->match($request);
expect($context->match)->toBeInstanceOf(RouteMatchSuccess::class);
if ($context->match instanceof RouteMatchSuccess) {
expect($context->match->route->controller)->toBe('ExactController');
}
});
it('compiles dynamic routes with subdomain patterns', function () {
$compiler = new RouteCompiler();
$dynamicRoute = new DiscoveredAttribute(
className: ClassName::create('ApiController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('getUser'),
arguments: [
'path' => '/api/users/{id}',
'method' => Method::GET,
'subdomain' => 'api',
],
additionalData: ['parameters' => []]
);
$compiled = $compiler->compile($dynamicRoute);
expect($compiled['GET']['exact:api']['dynamic'])->toHaveCount(1)
->and($compiled['GET']['exact:api']['dynamic'][0]->path)->toBe('/api/users/{id}')
->and($compiled['GET']['exact:api']['dynamic'][0]->paramNames)->toBe(['id']);
});
it('extracts subdomain correctly from various host patterns', function () {
$compiler = new RouteCompiler();
$testRoute = new DiscoveredAttribute(
className: ClassName::create('TestController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('test'),
arguments: [
'path' => '/test',
'method' => Method::GET,
'subdomain' => 'api',
],
additionalData: ['parameters' => []]
);
$compiledRoutes = $compiler->compileOptimized($testRoute);
$router = new HttpRouter($compiledRoutes);
// Test various host patterns
$testCases = [
'api.example.com' => 'api',
'www.example.com' => '', // www is ignored
'example.com' => '', // no subdomain
'sub.api.example.com' => 'sub.api', // full subdomain part
'test.localhost' => 'test', // development domain
'localhost' => '', // main localhost
];
foreach ($testCases as $host => $expectedSubdomain) {
$request = new HttpRequest(
method: Method::GET,
path: '/test',
headers: new Headers(['Host' => $host]),
server: new ServerEnvironment(['HTTP_HOST' => $host])
);
// Use reflection to test the private extractSubdomain method
$reflection = new ReflectionClass($router);
$method = $reflection->getMethod('extractSubdomain');
$method->setAccessible(true);
$result = $method->invoke($router, $host);
expect($result)->toBe($expectedSubdomain, "Failed for host: $host");
}
});
it('default routes are NOT accessible via subdomain', function () {
$compiler = new RouteCompiler();
// Create a default route (no subdomain specified)
$defaultRoute = new DiscoveredAttribute(
className: ClassName::create('HomeController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('index'),
arguments: [
'path' => '/home',
'method' => Method::GET,
'subdomain' => [], // No subdomain = default route
],
additionalData: ['parameters' => []]
);
$compiledRoutes = $compiler->compileOptimized($defaultRoute);
$router = new HttpRouter($compiledRoutes);
// Test 1: Should work on main domain (no subdomain)
$mainDomainRequest = new HttpRequest(
method: Method::GET,
path: '/home',
headers: new Headers(['Host' => 'example.com']),
server: new ServerEnvironment(['HTTP_HOST' => 'example.com'])
);
$mainContext = $router->match($mainDomainRequest);
expect($mainContext->match)->toBeInstanceOf(RouteMatchSuccess::class);
// Test 2: Should NOT work on subdomain
$subdomainRequest = new HttpRequest(
method: Method::GET,
path: '/home',
headers: new Headers(['Host' => 'api.example.com']),
server: new ServerEnvironment(['HTTP_HOST' => 'api.example.com'])
);
$subdomainContext = $router->match($subdomainRequest);
expect($subdomainContext->match)->toBeInstanceOf(\App\Framework\Router\NoRouteMatch::class);
});
it('subdomain-specific routes are NOT accessible via main domain', function () {
$compiler = new RouteCompiler();
// Create a subdomain-specific route
$apiRoute = new DiscoveredAttribute(
className: ClassName::create('ApiController'),
attributeClass: Route::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('endpoint'),
arguments: [
'path' => '/api/data',
'method' => Method::GET,
'subdomain' => 'api', // Only accessible via api.domain.com
],
additionalData: ['parameters' => []]
);
$compiledRoutes = $compiler->compileOptimized($apiRoute);
$router = new HttpRouter($compiledRoutes);
// Test 1: Should work on correct subdomain
$correctSubdomainRequest = new HttpRequest(
method: Method::GET,
path: '/api/data',
headers: new Headers(['Host' => 'api.example.com']),
server: new ServerEnvironment(['HTTP_HOST' => 'api.example.com'])
);
$correctContext = $router->match($correctSubdomainRequest);
expect($correctContext->match)->toBeInstanceOf(RouteMatchSuccess::class);
// Test 2: Should NOT work on main domain
$mainDomainRequest = new HttpRequest(
method: Method::GET,
path: '/api/data',
headers: new Headers(['Host' => 'example.com']),
server: new ServerEnvironment(['HTTP_HOST' => 'example.com'])
);
$mainContext = $router->match($mainDomainRequest);
expect($mainContext->match)->toBeInstanceOf(\App\Framework\Router\NoRouteMatch::class);
// Test 3: Should NOT work on wrong subdomain
$wrongSubdomainRequest = new HttpRequest(
method: Method::GET,
path: '/api/data',
headers: new Headers(['Host' => 'admin.example.com']),
server: new ServerEnvironment(['HTTP_HOST' => 'admin.example.com'])
);
$wrongContext = $router->match($wrongSubdomainRequest);
expect($wrongContext->match)->toBeInstanceOf(\App\Framework\Router\NoRouteMatch::class);
});