Files
michaelschiemer/tests/Unit/Framework/LiveComponents/Performance/ActionProfilerTest.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

319 lines
12 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\DateTime\SystemClock;
use App\Framework\DateTime\SystemHighResolutionClock;
use App\Framework\LiveComponents\Performance\ActionProfiler;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\Performance\NestedPerformanceTracker;
/**
* Unit Tests for ActionProfiler
*
* Tests ActionProfiler's capabilities for profiling LiveComponent actions:
* - CSRF validation profiling
* - Authorization check profiling
* - Rate limit check profiling
* - Parameter binding profiling
* - Idempotency check profiling
* - Action metrics aggregation
* - Component-level summaries
* - System-wide performance reports
*/
describe('ActionProfiler', function () {
beforeEach(function () {
$this->tracker = new NestedPerformanceTracker(
new SystemClock(),
new SystemHighResolutionClock(),
new MemoryMonitor()
);
$this->profiler = new ActionProfiler($this->tracker);
$this->componentId = ComponentId::create('test-component', 'instance-1');
});
it('profiles CSRF validation', function () {
$this->profiler->profileCsrfValidation(
$this->componentId,
'testAction',
function () {
usleep(1000); // 1ms
return true;
}
);
$timeline = $this->tracker->generateTimeline();
expect($timeline)->not->toBeEmpty();
$csrfEvents = array_filter($timeline, fn($e) => str_contains($e['name'], 'csrf'));
expect($csrfEvents)->not->toBeEmpty();
$event = array_values($csrfEvents)[0];
expect($event['duration_ms'])->toBeGreaterThan(0.5);
expect($event)->toHaveKey('context');
expect($event['context'])->toHaveKey('component');
expect($event['context']['component'])->toBe('test-component');
});
it('profiles authorization check', function () {
$this->profiler->profileAuthorizationCheck(
$this->componentId,
'testAction',
function () {
usleep(500); // 0.5ms
return true;
}
);
$timeline = $this->tracker->generateTimeline();
$authEvents = array_filter($timeline, fn($e) => str_contains($e['name'], 'authorization'));
expect($authEvents)->not->toBeEmpty();
$event = array_values($authEvents)[0];
expect($event['context'])->toHaveKey('action');
expect($event['context']['action'])->toBe('testAction');
});
it('profiles rate limit check', function () {
$this->profiler->profileRateLimitCheck(
$this->componentId,
'testAction',
function () {
usleep(300); // 0.3ms
return true;
}
);
$timeline = $this->tracker->generateTimeline();
$rateLimitEvents = array_filter($timeline, fn($e) => str_contains($e['name'], 'rateLimit'));
expect($rateLimitEvents)->not->toBeEmpty();
});
it('profiles parameter binding', function () {
$this->profiler->profileParameterBinding(
$this->componentId,
'testAction',
function () {
usleep(2000); // 2ms - reflection overhead
return ['param1' => 'value1'];
}
);
$timeline = $this->tracker->generateTimeline();
$bindingEvents = array_filter($timeline, fn($e) => str_contains($e['name'], 'binding'));
expect($bindingEvents)->not->toBeEmpty();
$event = array_values($bindingEvents)[0];
expect($event['duration_ms'])->toBeGreaterThan(1);
});
it('profiles idempotency check', function () {
$this->profiler->profileIdempotencyCheck(
$this->componentId,
'testAction',
function () {
usleep(400); // 0.4ms - cache lookup
return null; // No cached result
}
);
$timeline = $this->tracker->generateTimeline();
$idempotencyEvents = array_filter($timeline, fn($e) => str_contains($e['name'], 'idempotency'));
expect($idempotencyEvents)->not->toBeEmpty();
});
it('aggregates action metrics across multiple executions', function () {
$component1 = ComponentId::create('counter', 'inst-1');
// Execute multiple actions WITHOUT reset to accumulate metrics
for ($i = 0; $i < 3; $i++) {
$this->profiler->profileCsrfValidation($component1, 'increment', fn() => usleep(100));
$this->profiler->profileAuthorizationCheck($component1, 'increment', fn() => usleep(50));
}
// Get metrics for specific action
$counterMetrics = $this->profiler->getActionMetrics('counter', 'increment');
expect($counterMetrics)->toHaveKey('component');
expect($counterMetrics)->toHaveKey('action');
expect($counterMetrics)->toHaveKey('phase_timings');
expect($counterMetrics['component'])->toBe('counter');
expect($counterMetrics['action'])->toBe('increment');
// Verify we tracked multiple phases
expect($counterMetrics['phase_timings'])->not->toBeEmpty();
});
it('provides component-level performance summary', function () {
$component = ComponentId::create('user-profile', 'user-123');
// Multiple actions on same component WITHOUT reset
$this->profiler->profileCsrfValidation($component, 'update', fn() => usleep(100));
$this->profiler->profileParameterBinding($component, 'update', fn() => usleep(200));
$this->profiler->profileCsrfValidation($component, 'delete', fn() => usleep(100));
$this->profiler->profileAuthorizationCheck($component, 'delete', fn() => usleep(150));
$summary = $this->profiler->getComponentSummary('user-profile');
expect($summary)->toHaveKey('component');
expect($summary)->toHaveKey('total_operations');
expect($summary)->toHaveKey('total_time_ms');
expect($summary)->toHaveKey('operations');
expect($summary['component'])->toBe('user-profile');
expect($summary['total_operations'])->toBeGreaterThan(0);
expect($summary['operations'])->toBeArray();
});
it('generates system-wide performance report', function () {
$comp1 = ComponentId::create('component-a', 'a-1');
$comp2 = ComponentId::create('component-b', 'b-1');
$comp3 = ComponentId::create('component-c', 'c-1');
// Simulate multiple components and actions WITHOUT reset
$this->profiler->profileCsrfValidation($comp1, 'action1', fn() => usleep(100));
$this->profiler->profileAuthorizationCheck($comp2, 'action2', fn() => usleep(150));
$this->profiler->profileRateLimitCheck($comp3, 'action3', fn() => usleep(75));
$report = $this->profiler->generatePerformanceReport();
expect($report)->toHaveKey('generated_at');
expect($report)->toHaveKey('total_components');
expect($report)->toHaveKey('total_operations');
expect($report)->toHaveKey('components');
expect($report['total_components'])->toBe(3);
expect($report['components'])->toHaveKey('component-a');
expect($report['components'])->toHaveKey('component-b');
expect($report['components'])->toHaveKey('component-c');
});
it('tracks execution times for action phases', function () {
$component = ComponentId::create('fast-component', 'fast-1');
// Execute action multiple times
for ($i = 0; $i < 3; $i++) {
$this->profiler->profileCsrfValidation($component, 'quickAction', fn() => usleep(100));
}
$metrics = $this->profiler->getActionMetrics('fast-component', 'quickAction');
expect($metrics)->toHaveKey('phase_timings');
expect($metrics['phase_timings'])->not->toBeEmpty();
// Verify phase timing structure
$firstPhase = array_values($metrics['phase_timings'])[0];
expect($firstPhase)->toHaveKey('count');
expect($firstPhase)->toHaveKey('total_ms');
expect($firstPhase)->toHaveKey('avg_ms');
expect($firstPhase['count'])->toBe(3);
});
it('calculates average execution time for phases', function () {
$component = ComponentId::create('slow-component', 'slow-1');
// Execute multiple times
for ($i = 0; $i < 3; $i++) {
$this->profiler->profileParameterBinding($component, 'complexAction', fn() => usleep(1000));
}
$metrics = $this->profiler->getActionMetrics('slow-component', 'complexAction');
expect($metrics)->toHaveKey('phase_timings');
expect($metrics['phase_timings'])->not->toBeEmpty();
// Verify average is calculated correctly
$firstPhase = array_values($metrics['phase_timings'])[0];
expect($firstPhase['avg_ms'])->toBeGreaterThan(0);
expect($firstPhase['avg_ms'])->toBe($firstPhase['total_ms'] / $firstPhase['count']);
});
it('tracks total execution count per phase', function () {
$component = ComponentId::create('counted-component', 'count-1');
// Execute 5 times WITHOUT reset
for ($i = 0; $i < 5; $i++) {
$this->profiler->profileCsrfValidation($component, 'countedAction', fn() => usleep(100));
}
$metrics = $this->profiler->getActionMetrics('counted-component', 'countedAction');
expect($metrics)->toHaveKey('phase_timings');
expect($metrics['phase_timings'])->not->toBeEmpty();
// Verify count tracking
$firstPhase = array_values($metrics['phase_timings'])[0];
expect($firstPhase['count'])->toBe(5);
});
it('handles components with no profiled actions gracefully', function () {
$summary = $this->profiler->getComponentSummary('non-existent-component');
expect($summary['component'])->toBe('non-existent-component');
expect($summary['total_operations'])->toBe(0);
expect($summary['total_time_ms'])->toBe(0);
expect($summary['operations'])->toBe([]);
});
it('handles actions with no profiled phases gracefully', function () {
$metrics = $this->profiler->getActionMetrics('non-existent', 'action');
expect($metrics['component'])->toBe('non-existent');
expect($metrics['action'])->toBe('action');
expect($metrics['executions'])->toBe(0);
expect($metrics['metrics'])->toBe([]);
});
it('measures overhead of profiling system', function () {
$component = ComponentId::create('overhead-test', 'ov-1');
// Measure baseline (no profiling) - execute noop lambda
$baselineStart = microtime(true);
for ($i = 0; $i < 100; $i++) {
$noop = function () {}; // Minimal work
$noop();
}
$baselineDuration = (microtime(true) - $baselineStart) * 1000;
// Measure with profiling
$this->tracker->reset();
$profiledStart = microtime(true);
for ($i = 0; $i < 100; $i++) {
$this->profiler->profileCsrfValidation($component, 'noop', fn() => null);
}
$profiledDuration = (microtime(true) - $profiledStart) * 1000;
// Overhead should be reasonable (<5ms for 100 operations)
$overhead = $profiledDuration - $baselineDuration;
expect($overhead)->toBeLessThan(10); // Maximum 10ms overhead for 100 operations
});
it('includes context data in all profiled operations', function () {
$component = ComponentId::create('context-test', 'ctx-1');
$this->profiler->profileCsrfValidation($component, 'contextAction', fn() => usleep(100));
$timeline = $this->tracker->generateTimeline();
foreach ($timeline as $event) {
expect($event)->toHaveKey('context');
expect($event['context'])->toHaveKey('component');
expect($event['context'])->toHaveKey('action');
}
});
});