structured)->toBe([]); expect($context->tags)->toBe([]); expect($context->trace)->toBeNull(); expect($context->user)->toBeNull(); expect($context->request)->toBeNull(); expect($context->metadata)->toBe([]); }); it('can be created with structured data', function () { $data = ['user_id' => 123, 'action' => 'login']; $context = LogContext::withData($data); expect($context->structured)->toBe($data); expect($context->hasStructuredData())->toBeTrue(); }); it('can be created with tags', function () { $context = LogContext::withTags('auth', 'user-action'); expect($context->tags)->toBe(['auth', 'user-action']); expect($context->hasTags())->toBeTrue(); expect($context->hasTag('auth'))->toBeTrue(); expect($context->hasTag('missing'))->toBeFalse(); }); it('can add structured data immutably', function () { $original = LogContext::withData(['key1' => 'value1']); $modified = $original->addData('key2', 'value2'); expect($original->structured)->toBe(['key1' => 'value1']); expect($modified->structured)->toBe(['key1' => 'value1', 'key2' => 'value2']); }); it('can merge structured data immutably', function () { $original = LogContext::withData(['key1' => 'value1']); $modified = $original->mergeData(['key2' => 'value2', 'key3' => 'value3']); expect($original->structured)->toBe(['key1' => 'value1']); expect($modified->structured)->toBe([ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', ]); }); it('can add tags immutably', function () { $original = LogContext::withTags('tag1'); $modified = $original->addTags('tag2', 'tag3'); expect($original->tags)->toBe(['tag1']); expect($modified->tags)->toBe(['tag1', 'tag2', 'tag3']); }); it('removes duplicate tags when adding', function () { $original = LogContext::withTags('tag1', 'tag2'); $modified = $original->addTags('tag2', 'tag3'); expect($modified->tags)->toBe(['tag1', 'tag2', 'tag3']); }); it('can add user context immutably', function () { $userContext = UserContext::authenticated('123', 'john'); $original = LogContext::empty(); $modified = $original->withUser($userContext); expect($original->user)->toBeNull(); expect($modified->user)->toBe($userContext); }); it('can add request context immutably', function () { $requestContext = RequestContext::empty(); $original = LogContext::empty(); $modified = $original->withRequest($requestContext); expect($original->request)->toBeNull(); expect($modified->request)->toBe($requestContext); }); it('can add trace context immutably', function () { $traceContext = TraceContext::start(new SecureRandomGenerator()); $original = LogContext::empty(); $modified = $original->withTrace($traceContext); expect($original->trace)->toBeNull(); expect($modified->trace)->toBe($traceContext); }); it('can add metadata immutably', function () { $original = LogContext::empty(); $modified = $original->addMetadata('source', 'test'); expect($original->metadata)->toBe([]); expect($modified->metadata)->toBe(['source' => 'test']); }); it('can merge two contexts', function () { $context1 = LogContext::withData(['key1' => 'value1']) ->addTags('tag1') ->addMetadata('meta1', 'value1'); $context2 = LogContext::withData(['key2' => 'value2']) ->addTags('tag2') ->addMetadata('meta2', 'value2'); $merged = $context1->merge($context2); expect($merged->structured)->toBe(['key1' => 'value1', 'key2' => 'value2']); expect($merged->tags)->toBe(['tag1', 'tag2']); expect($merged->metadata)->toBe(['meta1' => 'value1', 'meta2' => 'value2']); }); it('prioritizes second context in merge for trace, user, request', function () { $user1 = UserContext::authenticated('123'); $user2 = UserContext::authenticated('456'); $request1 = RequestContext::empty(); $request2 = RequestContext::empty(); $trace1 = TraceContext::start(new SecureRandomGenerator()); $trace2 = TraceContext::start(new SecureRandomGenerator()); $context1 = LogContext::empty() ->withUser($user1) ->withRequest($request1) ->withTrace($trace1); $context2 = LogContext::empty() ->withUser($user2) ->withRequest($request2) ->withTrace($trace2); $merged = $context1->merge($context2); expect($merged->user)->toBe($user2); expect($merged->request)->toBe($request2); expect($merged->trace)->toBe($trace2); }); it('can convert to array', function () { $userContext = UserContext::authenticated('123', 'john'); $requestContext = RequestContext::empty(); $traceContext = TraceContext::start(new SecureRandomGenerator()); $context = LogContext::withData(['key' => 'value']) ->addTags('tag1', 'tag2') ->withUser($userContext) ->withRequest($requestContext) ->withTrace($traceContext) ->addMetadata('source', 'test'); $array = $context->toArray(); expect($array)->toHaveKeys([ 'structured', 'tags', 'trace', 'user', 'request', 'metadata', ]); expect($array['structured'])->toBe(['key' => 'value']); expect($array['tags'])->toBe(['tag1', 'tag2']); expect($array['metadata'])->toBe(['source' => 'test']); }); it('only includes non-empty sections in toArray', function () { $context = LogContext::withData(['key' => 'value']); $array = $context->toArray(); expect($array)->toHaveKey('structured'); expect($array)->not->toHaveKey('tags'); expect($array)->not->toHaveKey('trace'); expect($array)->not->toHaveKey('user'); expect($array)->not->toHaveKey('request'); expect($array)->not->toHaveKey('metadata'); }); it('can create context with current trace', function () { // Clear any existing trace TraceContext::clear(); // Start a new trace $trace = TraceContext::start(new SecureRandomGenerator()); $context = LogContext::withCurrentTrace(); expect($context->trace)->toBe($trace); // Cleanup TraceContext::clear(); }); it('creates context with null trace when no current trace', function () { TraceContext::clear(); $context = LogContext::withCurrentTrace(); expect($context->trace)->toBeNull(); }); });