skipAggregation)->toBeFalse(); expect($metadata->skipReporting)->toBeFalse(); expect($metadata->reported)->toBeFalse(); expect($metadata->retryAfter)->toBeNull(); }); it('creates metadata with retry', function () { $metadata = ExceptionMetadata::withRetry(60); expect($metadata->retryAfter)->toBe(60); expect($metadata->skipAggregation)->toBeFalse(); }); }); describe('Immutability', function () { it('withSkipAggregation creates new instance', function () { $original = ExceptionMetadata::default(); $modified = $original->withSkipAggregation(); expect($original)->not->toBe($modified); expect($original->skipAggregation)->toBeFalse(); expect($modified->skipAggregation)->toBeTrue(); }); it('withSkipReporting creates new instance', function () { $original = ExceptionMetadata::default(); $modified = $original->withSkipReporting(); expect($original->skipReporting)->toBeFalse(); expect($modified->skipReporting)->toBeTrue(); }); it('withSkipAll creates new instance', function () { $original = ExceptionMetadata::default(); $modified = $original->withSkipAll(); expect($modified->skipAggregation)->toBeTrue(); expect($modified->skipReporting)->toBeTrue(); }); it('markAsReported creates new instance', function () { $original = ExceptionMetadata::default(); $modified = $original->markAsReported(); expect($original->reported)->toBeFalse(); expect($modified->reported)->toBeTrue(); }); it('withRetryAfter creates new instance', function () { $original = ExceptionMetadata::default(); $modified = $original->withRetryAfter(30); expect($original->retryAfter)->toBeNull(); expect($modified->retryAfter)->toBe(30); }); }); describe('Behavior Control', function () { it('shouldAggregate returns correct value', function () { $default = ExceptionMetadata::default(); $skipped = $default->withSkipAggregation(); expect($default->shouldAggregate())->toBeTrue(); expect($skipped->shouldAggregate())->toBeFalse(); }); it('shouldReport considers both flags', function () { $default = ExceptionMetadata::default(); $skipReporting = $default->withSkipReporting(); $reported = $default->markAsReported(); expect($default->shouldReport())->toBeTrue(); expect($skipReporting->shouldReport())->toBeFalse(); expect($reported->shouldReport())->toBeFalse(); }); it('wasReported returns correct value', function () { $notReported = ExceptionMetadata::default(); $reported = $notReported->markAsReported(); expect($notReported->wasReported())->toBeFalse(); expect($reported->wasReported())->toBeTrue(); }); }); describe('Chaining', function () { it('chains multiple modifications', function () { $metadata = ExceptionMetadata::default() ->withSkipAggregation() ->withRetryAfter(60); expect($metadata->skipAggregation)->toBeTrue(); expect($metadata->retryAfter)->toBe(60); expect($metadata->skipReporting)->toBeFalse(); }); it('preserves existing values when chaining', function () { $metadata = ExceptionMetadata::withRetry(30) ->withSkipReporting(); expect($metadata->retryAfter)->toBe(30); expect($metadata->skipReporting)->toBeTrue(); expect($metadata->skipAggregation)->toBeFalse(); }); }); describe('Serialization', function () { it('converts to array', function () { $metadata = ExceptionMetadata::default() ->withSkipAggregation() ->withRetryAfter(60); $array = $metadata->toArray(); expect($array)->toBe([ 'skip_aggregation' => true, 'skip_reporting' => false, 'reported' => false, 'retry_after' => 60, ]); }); it('creates from array', function () { $array = [ 'skip_aggregation' => true, 'skip_reporting' => true, 'reported' => false, 'retry_after' => 30, ]; $metadata = ExceptionMetadata::fromArray($array); expect($metadata->skipAggregation)->toBeTrue(); expect($metadata->skipReporting)->toBeTrue(); expect($metadata->reported)->toBeFalse(); expect($metadata->retryAfter)->toBe(30); }); it('handles missing array keys with defaults', function () { $metadata = ExceptionMetadata::fromArray([]); expect($metadata->skipAggregation)->toBeFalse(); expect($metadata->skipReporting)->toBeFalse(); expect($metadata->reported)->toBeFalse(); expect($metadata->retryAfter)->toBeNull(); }); it('roundtrips through array', function () { $original = ExceptionMetadata::default() ->withSkipAll() ->withRetryAfter(120) ->markAsReported(); $array = $original->toArray(); $restored = ExceptionMetadata::fromArray($array); expect($restored->skipAggregation)->toBe($original->skipAggregation); expect($restored->skipReporting)->toBe($original->skipReporting); expect($restored->reported)->toBe($original->reported); expect($restored->retryAfter)->toBe($original->retryAfter); }); }); describe('Edge Cases', function () { it('handles null retry after', function () { $metadata = ExceptionMetadata::default() ->withRetryAfter(60) ->withRetryAfter(null); expect($metadata->retryAfter)->toBeNull(); }); it('handles zero retry after', function () { $metadata = ExceptionMetadata::withRetry(0); expect($metadata->retryAfter)->toBe(0); }); it('handles negative retry after', function () { $metadata = ExceptionMetadata::withRetry(-1); expect($metadata->retryAfter)->toBe(-1); }); it('marking as reported multiple times is idempotent', function () { $metadata = ExceptionMetadata::default() ->markAsReported() ->markAsReported(); expect($metadata->reported)->toBeTrue(); expect($metadata->shouldReport())->toBeFalse(); }); }); describe('Integration Scenarios', function () { it('supports typical exception lifecycle', function () { // 1. Create exception with default metadata $metadata = ExceptionMetadata::default(); expect($metadata->shouldReport())->toBeTrue(); expect($metadata->shouldAggregate())->toBeTrue(); // 2. Skip aggregation for specific case $metadata = $metadata->withSkipAggregation(); expect($metadata->shouldAggregate())->toBeFalse(); expect($metadata->shouldReport())->toBeTrue(); // 3. Mark as reported after logging $metadata = $metadata->markAsReported(); expect($metadata->wasReported())->toBeTrue(); expect($metadata->shouldReport())->toBeFalse(); }); it('supports retry scenario', function () { // 1. Create with retry hint $metadata = ExceptionMetadata::withRetry(60); expect($metadata->retryAfter)->toBe(60); // 2. Update retry time $metadata = $metadata->withRetryAfter(30); expect($metadata->retryAfter)->toBe(30); // 3. Clear retry $metadata = $metadata->withRetryAfter(null); expect($metadata->retryAfter)->toBeNull(); }); it('supports skip all for internal exceptions', function () { $metadata = ExceptionMetadata::default() ->withSkipAll(); expect($metadata->shouldAggregate())->toBeFalse(); expect($metadata->shouldReport())->toBeFalse(); expect($metadata->wasReported())->toBeFalse(); }); }); });