Files
michaelschiemer/tests/Unit/Framework/LiveComponents/Tracing/LiveComponentTracerTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

500 lines
17 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\LiveComponents\Tracing\LiveComponentTracer;
use App\Framework\Telemetry\UnifiedTelemetryService;
use App\Framework\Telemetry\OperationHandle;
use App\Framework\Telemetry\Config\TelemetryConfig;
use App\Framework\Tracing\TraceSpan;
use App\Framework\Tracing\SpanStatus;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
use App\Framework\CircuitBreaker\CircuitBreaker;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\SystemClock;
use App\Framework\Cache\Driver\InMemoryCache;
use App\Framework\Cache\GeneralCache;
use App\Framework\Serializer\Serializer;
use App\Framework\Logging\DefaultLogger;
describe('LiveComponentTracer', function () {
beforeEach(function () {
// Create telemetry service with enabled configuration
$this->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() => '<div>User Card</div>'
);
expect($html)->toBe('<div>User Card</div>');
});
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');
});
});
});