contextManager = new LogContextManager(); TraceContext::clear(); }); afterEach(function () { // Clean up after each test TraceContext::clear(); }); it('starts with empty context', function () { $context = $this->contextManager->getCurrentContext(); expect($context->structured)->toBe([]); expect($context->tags)->toBe([]); expect($context->trace)->toBeNull(); expect($context->user)->toBeNull(); expect($context->request)->toBeNull(); }); it('can set and get global context', function () { $globalContext = LogContext::withData(['app' => 'test']) ->addTags('global'); $this->contextManager->setGlobalContext($globalContext); $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['app' => 'test']); expect($current->tags)->toBe(['global']); }); it('can set and get request context', function () { $requestContext = LogContext::withData(['request' => 'test']) ->addTags('request'); $this->contextManager->setRequestContext($requestContext); $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['request' => 'test']); expect($current->tags)->toBe(['request']); }); it('merges global and request contexts', function () { $globalContext = LogContext::withData(['app' => 'test']) ->addTags('global'); $requestContext = LogContext::withData(['request' => 'test']) ->addTags('request'); $this->contextManager->setGlobalContext($globalContext); $this->contextManager->setRequestContext($requestContext); $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['app' => 'test', 'request' => 'test']); expect($current->tags)->toBe(['global', 'request']); }); it('can add data to global context', function () { $this->contextManager->addGlobalData('key1', 'value1'); $this->contextManager->addGlobalData('key2', 'value2'); $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['key1' => 'value1', 'key2' => 'value2']); }); it('can add tags to global context', function () { $this->contextManager->addGlobalTags('tag1', 'tag2'); $this->contextManager->addGlobalTags('tag3'); $current = $this->contextManager->getCurrentContext(); expect($current->tags)->toBe(['tag1', 'tag2', 'tag3']); }); it('can add data to request context', function () { $this->contextManager->addRequestData('req1', 'value1'); $this->contextManager->addRequestData('req2', 'value2'); $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['req1' => 'value1', 'req2' => 'value2']); }); it('can add tags to request context', function () { $this->contextManager->addRequestTags('reqtag1', 'reqtag2'); $this->contextManager->addRequestTags('reqtag3'); $current = $this->contextManager->getCurrentContext(); expect($current->tags)->toBe(['reqtag1', 'reqtag2', 'reqtag3']); }); it('includes current trace context automatically', function () { $trace = TraceContext::start(new SecureRandomGenerator()); $current = $this->contextManager->getCurrentContext(); expect($current->trace)->toBe($trace); }); it('can add user context', function () { $userContext = UserContext::authenticated('123', 'john'); $this->contextManager->withUser($userContext); $current = $this->contextManager->getCurrentContext(); expect($current->user)->toBe($userContext); }); it('can add request context through manager', function () { $requestContext = RequestContext::empty(); $this->contextManager->withRequest($requestContext); $current = $this->contextManager->getCurrentContext(); expect($current->request)->toBe($requestContext); }); it('can add trace context through manager', function () { $traceContext = TraceContext::start(new SecureRandomGenerator()); $this->contextManager->withTrace($traceContext); $current = $this->contextManager->getCurrentContext(); expect($current->trace)->toBe($traceContext); }); it('can clear request context only', function () { $this->contextManager->setGlobalContext(LogContext::withData(['global' => 'data'])); $this->contextManager->setRequestContext(LogContext::withData(['request' => 'data'])); $this->contextManager->clearRequestContext(); $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['global' => 'data']); }); it('can clear global context only', function () { $this->contextManager->setGlobalContext(LogContext::withData(['global' => 'data'])); $this->contextManager->setRequestContext(LogContext::withData(['request' => 'data'])); $this->contextManager->clearGlobalContext(); $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['request' => 'data']); }); it('can clear all contexts', function () { $this->contextManager->setGlobalContext(LogContext::withData(['global' => 'data'])); $this->contextManager->setRequestContext(LogContext::withData(['request' => 'data'])); $this->contextManager->clearContext(); $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['global' => 'data']); // Global remains }); it('can execute callback with temporary context', function () { $originalData = ['original' => 'data']; $this->contextManager->setRequestContext(LogContext::withData($originalData)); $temporaryContext = LogContext::withData(['temp' => 'data']); $result = $this->contextManager->withTemporaryContext($temporaryContext, function () { $current = $this->contextManager->getCurrentContext(); return $current->structured; }); expect($result)->toBe(['original' => 'data', 'temp' => 'data']); // Original context should be restored $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe($originalData); }); it('can execute callback with temporary tags', function () { $this->contextManager->setRequestContext(LogContext::withTags('original')); $result = $this->contextManager->withTemporaryTags(['temp1', 'temp2'], function () { $current = $this->contextManager->getCurrentContext(); return $current->tags; }); expect($result)->toBe(['original', 'temp1', 'temp2']); // Original context should be restored $current = $this->contextManager->getCurrentContext(); expect($current->tags)->toBe(['original']); }); it('can execute callback with temporary data', function () { $this->contextManager->setRequestContext(LogContext::withData(['original' => 'value'])); $result = $this->contextManager->withTemporaryData(['temp' => 'value'], function () { $current = $this->contextManager->getCurrentContext(); return $current->structured; }); expect($result)->toBe(['original' => 'value', 'temp' => 'value']); // Original context should be restored $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['original' => 'value']); }); it('restores context even when callback throws exception', function () { $originalContext = LogContext::withData(['original' => 'data']); $this->contextManager->setRequestContext($originalContext); $temporaryContext = LogContext::withData(['temp' => 'data']); try { $this->contextManager->withTemporaryContext($temporaryContext, function () { throw new \RuntimeException('Test exception'); }); } catch (\RuntimeException $e) { // Expected } // Original context should be restored $current = $this->contextManager->getCurrentContext(); expect($current->structured)->toBe(['original' => 'data']); }); it('can initialize request context from globals', function () { // Mock global variables $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_URI'] = '/test'; $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['SERVER_NAME'] = 'example.com'; $_SERVER['SERVER_PORT'] = '80'; // Test direct RequestContext creation first $requestContext = \App\Framework\Logging\ValueObjects\RequestContext::fromGlobals(); expect($requestContext)->not->toBeNull(); expect($requestContext->getMethod())->toBe('GET'); $context = $this->contextManager->initializeRequestContext(); expect($context->request)->not->toBeNull(); expect($context->request->getMethod())->toBe('GET'); expect($context->request->getUri())->toBe('/test'); expect($context->request->getHost())->toBe('example.com'); // Should be set as current request context $current = $this->contextManager->getCurrentContext(); expect($current->request)->toBe($context->request); // Cleanup unset($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT']); }); it('provides debug information', function () { $this->contextManager->setGlobalContext(LogContext::withData(['global' => 'data'])); $this->contextManager->setRequestContext(LogContext::withData(['request' => 'data'])); $debug = $this->contextManager->getDebugInfo(); expect($debug)->toHaveKeys([ 'has_global_context', 'has_request_context', 'global_context', 'request_context', 'current_trace', 'combined_context', ]); expect($debug['has_global_context'])->toBeTrue(); expect($debug['has_request_context'])->toBeTrue(); }); it('can check for trace context', function () { expect($this->contextManager->hasTraceContext())->toBeFalse(); TraceContext::start(new SecureRandomGenerator()); expect($this->contextManager->hasTraceContext())->toBeTrue(); }); it('can check for user context', function () { expect($this->contextManager->hasUserContext())->toBeFalse(); $this->contextManager->withUser(UserContext::authenticated('123')); expect($this->contextManager->hasUserContext())->toBeTrue(); }); it('can check for request context', function () { expect($this->contextManager->hasRequestContext())->toBeFalse(); $this->contextManager->withRequest(RequestContext::empty()); expect($this->contextManager->hasRequestContext())->toBeTrue(); }); });