Files
michaelschiemer/tests/Unit/Framework/ExceptionHandling/ExceptionContextIntegrationTest.php
Michael Schiemer 36ef2a1e2c
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
fix: Gitea Traefik routing and connection pool optimization
- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
2025-11-09 14:46:15 +01:00

254 lines
8.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\ExceptionHandling\Context\ExceptionContextData;
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
use App\Framework\ExceptionHandling\Factory\ExceptionFactory;
use App\Framework\ExceptionHandling\Scope\ErrorScope;
use App\Framework\ExceptionHandling\Scope\ErrorScopeContext;
describe('Exception Context Integration', function () {
beforeEach(function () {
$this->contextProvider = ExceptionContextProvider::instance();
$this->errorScope = new ErrorScope();
$this->factory = new ExceptionFactory($this->contextProvider, $this->errorScope);
// Clear any existing contexts
$this->contextProvider->clear();
});
it('creates slim exception with external context via WeakMap', function () {
$context = ExceptionContextData::forOperation('user.create', 'UserService')
->addData(['user_id' => '123', 'email' => 'test@example.com']);
$exception = $this->factory->create(
RuntimeException::class,
'User creation failed',
$context
);
// Exception is slim (pure PHP)
expect($exception)->toBeInstanceOf(RuntimeException::class);
expect($exception->getMessage())->toBe('User creation failed');
// Context is stored externally
$storedContext = $this->contextProvider->get($exception);
expect($storedContext)->not->toBeNull();
expect($storedContext->operation)->toBe('user.create');
expect($storedContext->component)->toBe('UserService');
expect($storedContext->data)->toBe([
'user_id' => '123',
'email' => 'test@example.com'
]);
});
it('automatically enriches context from error scope', function () {
// Enter HTTP scope
$scopeContext = ErrorScopeContext::http(
request: createMockRequest(),
operation: 'api.request',
component: 'ApiController'
)->withUserId('user-456');
$this->errorScope->enter($scopeContext);
// Create exception without explicit context
$exception = $this->factory->create(
RuntimeException::class,
'API request failed'
);
// Context is enriched from scope
$storedContext = $this->contextProvider->get($exception);
expect($storedContext)->not->toBeNull();
expect($storedContext->userId)->toBe('user-456');
expect($storedContext->metadata)->toHaveKey('scope_type');
expect($storedContext->metadata['scope_type'])->toBe('http');
});
it('supports WeakMap automatic garbage collection', function () {
$exception = new RuntimeException('Test exception');
$context = ExceptionContextData::forOperation('test.operation');
$this->contextProvider->attach($exception, $context);
// Context exists
expect($this->contextProvider->has($exception))->toBeTrue();
// Unset exception reference
unset($exception);
// Force garbage collection
gc_collect_cycles();
// WeakMap automatically cleaned up (we can't directly test this,
// but stats should reflect fewer contexts after GC)
$stats = $this->contextProvider->getStats();
expect($stats)->toHaveKey('total_contexts');
});
it('enhances existing exception with additional context', function () {
$exception = new RuntimeException('Original error');
$originalContext = ExceptionContextData::forOperation('operation.1')
->addData(['step' => 1]);
$this->contextProvider->attach($exception, $originalContext);
// Enhance with additional context
$additionalContext = ExceptionContextData::empty()
->addData(['step' => 2, 'error_code' => 'E001']);
$this->factory->enhance($exception, $additionalContext);
// Context is merged
$storedContext = $this->contextProvider->get($exception);
expect($storedContext->data)->toBe([
'step' => 2, // Overwrites
'error_code' => 'E001' // Adds
]);
});
it('supports fiber-aware error scopes', function () {
// Main scope
$mainScope = ErrorScopeContext::generic(
'main',
'main.operation'
);
$this->errorScope->enter($mainScope);
// Create fiber scope
$fiber = new Fiber(function () {
$fiberScope = ErrorScopeContext::generic(
'fiber',
'fiber.operation'
);
$this->errorScope->enter($fiberScope);
$exception = $this->factory->create(
RuntimeException::class,
'Fiber error'
);
// Context from fiber scope
$context = $this->contextProvider->get($exception);
expect($context->operation)->toBe('fiber.operation');
$this->errorScope->exit();
});
$fiber->start();
// Main scope still active
$exception = $this->factory->create(
RuntimeException::class,
'Main error'
);
$context = $this->contextProvider->get($exception);
expect($context->operation)->toBe('main.operation');
});
it('creates exception with convenience factory methods', function () {
// forOperation
$exception1 = $this->factory->forOperation(
InvalidArgumentException::class,
'Invalid user data',
'user.validate',
'UserValidator',
['email' => 'invalid']
);
$context1 = $this->contextProvider->get($exception1);
expect($context1->operation)->toBe('user.validate');
expect($context1->component)->toBe('UserValidator');
expect($context1->data['email'])->toBe('invalid');
// withData
$exception2 = $this->factory->withData(
RuntimeException::class,
'Database error',
['query' => 'SELECT * FROM users']
);
$context2 = $this->contextProvider->get($exception2);
expect($context2->data['query'])->toBe('SELECT * FROM users');
});
it('serializes Value Objects to strings in toArray()', function () {
$context = ExceptionContextData::forOperation('test.operation')
->withClientIp(\App\Framework\Http\IpAddress::from('192.168.1.1'))
->withUserAgent(\App\Framework\UserAgent\UserAgent::fromString('Mozilla/5.0'))
->withSessionId(\App\Framework\Http\Session\SessionId::fromString('session-12345678901234567890123456789012'))
->withRequestId('req-123');
$array = $context->toArray();
expect($array['client_ip'])->toBe('192.168.1.1');
expect($array['user_agent'])->toBe('Mozilla/5.0');
expect($array['session_id'])->toBe('session-12345678901234567890123456789012');
expect($array['request_id'])->toBe('req-123');
});
it('serializes strings to strings in toArray() (backward compatibility)', function () {
$context = ExceptionContextData::forOperation('test.operation')
->withClientIp('192.168.1.1')
->withUserAgent('Mozilla/5.0')
->withSessionId('session-123')
->withRequestId('req-123');
$array = $context->toArray();
expect($array['client_ip'])->toBe('192.168.1.1');
expect($array['user_agent'])->toBe('Mozilla/5.0');
expect($array['session_id'])->toBe('session-123');
expect($array['request_id'])->toBe('req-123');
});
it('handles nested error scopes correctly', function () {
// Outer scope
$outerScope = ErrorScopeContext::http(
request: createMockRequest(),
operation: 'outer.operation'
);
$this->errorScope->enter($outerScope);
// Inner scope
$innerScope = ErrorScopeContext::generic(
'inner',
'inner.operation'
);
$this->errorScope->enter($innerScope);
// Exception gets inner scope context
$exception = $this->factory->create(
RuntimeException::class,
'Inner error'
);
$context = $this->contextProvider->get($exception);
expect($context->operation)->toBe('inner.operation');
// Exit inner scope
$this->errorScope->exit();
// New exception gets outer scope context
$exception2 = $this->factory->create(
RuntimeException::class,
'Outer error'
);
$context2 = $this->contextProvider->get($exception2);
expect($context2->operation)->toBe('outer.operation');
});
});
// Helper function to create mock request
function createMockRequest(): \App\Framework\Http\HttpRequest
{
return new \App\Framework\Http\HttpRequest(
method: \App\Framework\Http\Method::GET,
path: '/test',
id: new \App\Framework\Http\RequestId('test-secret')
);
}