config = new TelemetryConfig( serviceName: 'test-service', serviceVersion: '1.0.0', environment: 'test', enabled: true ); // Create real instances for final classes $this->clock = new SystemClock(); $serializer = Mockery::mock(Serializer::class); $serializer->shouldReceive('serialize')->andReturnUsing(fn($val) => serialize($val)); $serializer->shouldReceive('unserialize')->andReturnUsing(fn($val) => unserialize($val)); $this->cache = new GeneralCache(new InMemoryCache(), $serializer); // Create CircuitBreaker with test dependencies $this->circuitBreaker = new CircuitBreaker($this->cache, $this->clock, null, null, null, 'test'); // Mock interfaces $this->performanceCollector = Mockery::mock(PerformanceCollectorInterface::class); // Configure mocks $this->performanceCollector->shouldReceive('startTiming')->andReturnNull(); $this->performanceCollector->shouldReceive('endTiming')->andReturnNull(); // Create simple logger for tests $this->logger = new DefaultLogger(); $this->telemetry = new UnifiedTelemetryService( $this->performanceCollector, $this->circuitBreaker, $this->logger, $this->clock, $this->config ); $this->tracer = new LiveComponentTracer($this->telemetry); }); afterEach(function () { Mockery::close(); }); describe('Resolve Operation Tracing', function () { it('creates trace span for component resolution', function () { $operation = $this->tracer->traceResolve('test-component', [ 'data_provider' => 'UserProvider', ]); expect($operation)->toBeInstanceOf(OperationHandle::class); // End the operation $operation->end('success'); }); it('includes component id in resolve attributes', function () { $operation = $this->tracer->traceResolve('user-list', [ 'initial_data' => ['page' => 1], ]); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); it('handles resolve operation errors gracefully', function () { $operation = $this->tracer->traceResolve('failing-component'); expect($operation)->toBeInstanceOf(OperationHandle::class); // End with error $operation->fail('Component resolution failed'); }); }); describe('Render Operation Tracing', function () { it('creates trace span for component rendering', function () { $operation = $this->tracer->traceRender('test-component', [ 'cached' => false, 'template' => 'components/user-card', ]); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); it('tracks render operation with view type', function () { $operation = $this->tracer->traceRender('product-list', [ 'items_count' => 50, ]); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); it('handles render failures with error status', function () { $operation = $this->tracer->traceRender('broken-template'); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->fail('Template rendering failed'); }); }); describe('Handle Operation Tracing', function () { it('creates trace span for action handling', function () { $operation = $this->tracer->traceHandle( 'form-component', 'submitForm', ['validation' => 'passed'] ); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); it('includes action name in handle attributes', function () { $operation = $this->tracer->traceHandle( 'cart-component', 'addItem', ['item_id' => 123] ); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); it('tracks action execution time', function () { $operation = $this->tracer->traceHandle('data-table', 'sortColumn'); expect($operation)->toBeInstanceOf(OperationHandle::class); // Simulate some work usleep(1000); // 1ms $operation->end('success'); }); }); describe('Upload Operation Tracing', function () { it('creates trace span for file upload', function () { $operation = $this->tracer->traceUpload( 'file-uploader', 'upload-session-123', ['chunk' => 1, 'total_chunks' => 5] ); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); it('includes upload session id in attributes', function () { $operation = $this->tracer->traceUpload( 'avatar-upload', 'session-abc-def', ['file_size' => 2048000] ); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); it('handles upload failures', function () { $operation = $this->tracer->traceUpload('doc-uploader', 'session-xyz'); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->fail('Upload failed: file too large'); }); }); describe('Event Recording', function () { it('records component lifecycle events', function () { // Events are fire-and-forget, no return value $this->tracer->recordEvent('mounted', 'test-component', [ 'mount_time_ms' => 45.2, ]); // Should not throw expect(true)->toBeTrue(); }); it('records multiple events for component', function () { $componentId = 'lifecycle-test'; $this->tracer->recordEvent('mounted', $componentId); $this->tracer->recordEvent('updated', $componentId, ['updates' => 3]); $this->tracer->recordEvent('destroyed', $componentId); expect(true)->toBeTrue(); }); it('records custom component events', function () { $this->tracer->recordEvent('form_submitted', 'contact-form', [ 'fields_count' => 5, 'validation_passed' => true, ]); expect(true)->toBeTrue(); }); }); describe('Metric Recording', function () { it('records component metrics', function () { $this->tracer->recordMetric('render_duration', 25.5, 'ms', [ 'component_id' => 'test-component', ]); expect(true)->toBeTrue(); }); it('records multiple metrics with different units', function () { $this->tracer->recordMetric('cache_size', 1024, 'bytes'); $this->tracer->recordMetric('action_count', 15, 'count'); $this->tracer->recordMetric('response_time', 150.3, 'ms'); expect(true)->toBeTrue(); }); it('records metrics without unit', function () { $this->tracer->recordMetric('items_rendered', 50.0); expect(true)->toBeTrue(); }); }); describe('Traced Execution', function () { it('executes callback within traced operation', function () { $result = $this->tracer->trace( 'resolve', 'test-component', fn() => ['data' => 'loaded'], ['source' => 'database'] ); expect($result)->toBe(['data' => 'loaded']); }); it('executes render operation with trace', function () { $html = $this->tracer->trace( 'render', 'user-card', fn() => '
User Card
' ); expect($html)->toBe('
User Card
'); }); it('executes handle operation with trace', function () { $result = $this->tracer->trace( 'handle', 'form-component', fn() => ['success' => true, 'message' => 'Form submitted'] ); expect($result['success'])->toBeTrue(); expect($result['message'])->toBe('Form submitted'); }); it('executes upload operation with trace', function () { $uploadResult = $this->tracer->trace( 'upload', 'file-uploader', fn() => ['uploaded' => true, 'file_id' => 123] ); expect($uploadResult['uploaded'])->toBeTrue(); expect($uploadResult['file_id'])->toBe(123); }); it('propagates exceptions from traced callbacks', function () { expect(fn() => $this->tracer->trace( 'render', 'broken-component', fn() => throw new \RuntimeException('Template not found') ))->toThrow(\RuntimeException::class, 'Template not found'); }); }); describe('Complex Tracing Scenarios', function () { it('handles nested tracing operations', function () { $result = $this->tracer->trace('resolve', 'parent-component', function() { // Nested render operation return $this->tracer->trace('render', 'child-component', function() { // Nested action handling return $this->tracer->trace('handle', 'nested-action', function() { return ['nested' => true]; }); }); }); expect($result['nested'])->toBeTrue(); }); it('traces complete component lifecycle', function () { $componentId = 'full-lifecycle'; // 1. Resolve $resolveOp = $this->tracer->traceResolve($componentId); $resolveOp->end('success'); // 2. Render $renderOp = $this->tracer->traceRender($componentId); $renderOp->end('success'); // 3. Handle action $handleOp = $this->tracer->traceHandle($componentId, 'onClick'); $handleOp->end('success'); // 4. Re-render $rerenderOp = $this->tracer->traceRender($componentId); $rerenderOp->end('success'); expect(true)->toBeTrue(); }); it('traces concurrent operations on different components', function () { $op1 = $this->tracer->traceRender('component-1'); $op2 = $this->tracer->traceRender('component-2'); $op3 = $this->tracer->traceHandle('component-3', 'action'); // End in different order $op2->end('success'); $op1->end('success'); $op3->end('success'); expect(true)->toBeTrue(); }); }); describe('Telemetry Integration', function () { it('checks if telemetry is enabled', function () { expect($this->tracer->isEnabled())->toBeTrue(); }); it('provides operation stack for debugging', function () { $this->tracer->trace('resolve', 'test-component', function() { $stack = $this->tracer->getOperationStack(); expect($stack)->toBeString(); }); }); it('handles telemetry disabled gracefully', function () { // Create tracer with disabled telemetry $disabledConfig = new TelemetryConfig( serviceName: 'test-service', serviceVersion: '1.0.0', environment: 'test', enabled: false ); $disabledTelemetry = new UnifiedTelemetryService( $this->performanceCollector, $this->circuitBreaker, $this->logger, $this->clock, $disabledConfig ); $disabledTracer = new LiveComponentTracer($disabledTelemetry); expect($disabledTracer->isEnabled())->toBeFalse(); // Operations should still work without throwing $op = $disabledTracer->traceResolve('test-component'); $op->end('success'); expect(true)->toBeTrue(); }); }); describe('Performance Characteristics', function () { it('has minimal overhead for tracing', function () { $start = microtime(true); for ($i = 0; $i < 100; $i++) { $op = $this->tracer->traceRender("component-{$i}"); $op->end('success'); } $duration = (microtime(true) - $start) * 1000; // ms // 100 operations should complete in reasonable time expect($duration)->toBeLessThan(100.0); // < 1ms per operation }); it('handles high-frequency event recording', function () { $start = microtime(true); for ($i = 0; $i < 1000; $i++) { $this->tracer->recordEvent('click', 'button-component'); } $duration = (microtime(true) - $start) * 1000; // ms // 1000 events should record quickly expect($duration)->toBeLessThan(200.0); // < 0.2ms per event }); }); describe('Error Handling', function () { it('records operation failures correctly', function () { $operation = $this->tracer->traceHandle('error-component', 'failingAction'); // Simulate failure $operation->fail('Action failed: validation error'); expect(true)->toBeTrue(); }); it('continues tracing after operation errors', function () { // First operation fails $op1 = $this->tracer->traceResolve('failing-component'); $op1->fail('Resolution failed'); // Subsequent operations should still work $op2 = $this->tracer->traceRender('working-component'); $op2->end('success'); expect(true)->toBeTrue(); }); it('handles exceptions in traced callbacks', function () { try { $this->tracer->trace('handle', 'exception-component', function() { throw new \RuntimeException('Business logic error'); }); } catch (\RuntimeException $e) { expect($e->getMessage())->toBe('Business logic error'); } // Tracing should still work after exception $op = $this->tracer->traceRender('recovery-component'); $op->end('success'); expect(true)->toBeTrue(); }); }); describe('Attributes and Context', function () { it('merges custom attributes with operation attributes', function () { $operation = $this->tracer->traceResolve('test-component', [ 'custom_attr1' => 'value1', 'custom_attr2' => 123, ]); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); it('preserves attribute types', function () { $operation = $this->tracer->traceHandle('type-test', 'action', [ 'string_attr' => 'text', 'int_attr' => 42, 'float_attr' => 3.14, 'bool_attr' => true, 'array_attr' => ['a', 'b', 'c'], ]); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); it('handles empty attributes gracefully', function () { $operation = $this->tracer->traceRender('minimal-component', []); expect($operation)->toBeInstanceOf(OperationHandle::class); $operation->end('success'); }); }); });