Files
michaelschiemer/tests/Framework/Queue/Integration/CompleteQueueSystemTest.php
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
2025-10-05 11:05:04 +02:00

372 lines
14 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Queue\Contracts\QueueInterface;
use App\Framework\Queue\Contracts\JobDependencyManagerInterface;
use App\Framework\Queue\Contracts\JobChainManagerInterface;
use App\Framework\Queue\Services\JobMetricsManager;
use App\Framework\Queue\Services\DependencyResolutionEngine;
use App\Framework\Queue\Services\JobChainExecutionCoordinator;
use App\Framework\Queue\ValueObjects\JobDependency;
use App\Framework\Queue\ValueObjects\JobChain;
use App\Framework\Queue\ValueObjects\JobMetrics;
use App\Framework\Queue\ValueObjects\ChainExecutionMode;
use App\Framework\Queue\Entities\JobProgressEntry;
use App\Framework\Queue\Entities\JobProgressStep;
use App\Framework\Database\EntityManagerInterface;
use App\Framework\Logging\Logger;
use App\Framework\Core\Application;
use App\Framework\DI\Container;
use App\Framework\Core\ValueObjects\Percentage;
beforeEach(function () {
// Set up test container
$this->container = createTestContainer();
// Get services from container
$this->queue = $this->container->get(QueueInterface::class);
$this->dependencyManager = $this->container->get(JobDependencyManagerInterface::class);
$this->chainManager = $this->container->get(JobChainManagerInterface::class);
$this->metricsManager = $this->container->get(JobMetricsManager::class);
$this->resolutionEngine = $this->container->get(DependencyResolutionEngine::class);
$this->chainCoordinator = $this->container->get(JobChainExecutionCoordinator::class);
$this->entityManager = $this->container->get(EntityManagerInterface::class);
$this->logger = $this->container->get(Logger::class);
});
function createTestJob(string $id, string $data): object
{
return new class($id, $data) {
public function __construct(
public readonly string $id,
public readonly string $data
) {}
};
}
test('complete queue workflow with dependencies and metrics', function () {
// 1. Create test jobs
$job1 = createTestJob('job-1', 'Test Job 1');
$job2 = createTestJob('job-2', 'Test Job 2');
$job3 = createTestJob('job-3', 'Test Job 3');
// 2. Set up dependencies: job2 depends on job1, job3 depends on job2
$dependency1 = JobDependency::completion('job-2', 'job-1');
$dependency2 = JobDependency::success('job-3', 'job-2');
// Add dependencies
$this->dependencyManager->addDependency($dependency1);
$this->dependencyManager->addDependency($dependency2);
// 3. Add jobs to queue
$this->queue->push($job1);
$this->queue->push($job2);
$this->queue->push($job3);
// 4. Create and record metrics for job execution
$job1Metrics = new JobMetrics(
jobId: 'job-1',
queueName: 'default',
status: 'completed',
attempts: 1,
maxAttempts: 3,
executionTimeMs: 150.5,
memoryUsageBytes: 1024 * 1024,
errorMessage: null,
createdAt: date('Y-m-d H:i:s'),
startedAt: date('Y-m-d H:i:s'),
completedAt: date('Y-m-d H:i:s'),
failedAt: null,
metadata: ['test' => true]
);
$this->metricsManager->recordJobMetrics($job1Metrics);
// 5. Test dependency resolution
$readyJobs = $this->resolutionEngine->getJobsReadyForExecution();
expect($readyJobs)->toHaveCount(1)
->and($readyJobs[0])->toBe('job-1');
// 6. Mark job1 as completed and check dependencies
$this->dependencyManager->markJobCompleted('job-1');
$readyJobsAfterJob1 = $this->resolutionEngine->getJobsReadyForExecution();
expect($readyJobsAfterJob1)->toContain('job-2');
// 7. Test metrics retrieval
$retrievedMetrics = $this->metricsManager->getJobMetrics('job-1');
expect($retrievedMetrics)->not()->toBeNull()
->and($retrievedMetrics->jobId)->toBe('job-1')
->and($retrievedMetrics->status)->toBe('completed')
->and($retrievedMetrics->executionTimeMs)->toBe(150.5);
// 8. Test queue metrics calculation
$queueMetrics = $this->metricsManager->getQueueMetrics('default', '1 hour');
expect($queueMetrics->queueName)->toBe('default')
->and($queueMetrics->totalJobs)->toBeGreaterThan(0);
});
test('job chain execution with sequential mode', function () {
// 1. Create jobs for chain
$jobs = [
createTestJob('chain-job-1', 'Chain Job 1'),
createTestJob('chain-job-2', 'Chain Job 2'),
createTestJob('chain-job-3', 'Chain Job 3')
];
// 2. Create job chain
$chain = JobChain::sequential('test-chain', ['chain-job-1', 'chain-job-2', 'chain-job-3']);
// 3. Add chain to manager
$this->chainManager->createChain($chain);
// 4. Execute chain
$this->chainCoordinator->executeChain('test-chain');
// 5. Verify chain was created
$retrievedChain = $this->chainManager->getChain('test-chain');
expect($retrievedChain)->not()->toBeNull()
->and($retrievedChain->name)->toBe('test-chain')
->and($retrievedChain->executionMode)->toBe(ChainExecutionMode::SEQUENTIAL)
->and($retrievedChain->jobIds)->toHaveCount(3);
});
test('job chain failure handling', function () {
// 1. Create jobs for chain with one that will fail
$jobs = [
createTestJob('fail-job-1', 'Job 1'),
createTestJob('fail-job-2', 'Job 2 (will fail)'),
createTestJob('fail-job-3', 'Job 3')
];
// 2. Create job chain with stop on failure
$chain = JobChain::sequential('fail-chain', ['fail-job-1', 'fail-job-2', 'fail-job-3']);
$this->chainManager->createChain($chain);
// 3. Simulate job failure
$failureMetrics = new JobMetrics(
jobId: 'fail-job-2',
queueName: 'default',
status: 'failed',
attempts: 3,
maxAttempts: 3,
executionTimeMs: 50.0,
memoryUsageBytes: 512 * 1024,
errorMessage: 'Simulated failure',
createdAt: date('Y-m-d H:i:s'),
startedAt: date('Y-m-d H:i:s'),
completedAt: null,
failedAt: date('Y-m-d H:i:s'),
metadata: []
);
$this->metricsManager->recordJobMetrics($failureMetrics);
// 4. Test failure detection
$failedJobs = $this->metricsManager->getFailedJobs('default', '1 hour');
expect($failedJobs)->toHaveCount(1)
->and($failedJobs[0]->jobId)->toBe('fail-job-2')
->and($failedJobs[0]->status)->toBe('failed');
});
test('circular dependency detection', function () {
// 1. Create circular dependencies: A depends on B, B depends on C, C depends on A
$depA = JobDependency::completion('job-a', 'job-b');
$depB = JobDependency::completion('job-b', 'job-c');
$depC = JobDependency::completion('job-c', 'job-a');
// 2. Add dependencies
$this->dependencyManager->addDependency($depA);
$this->dependencyManager->addDependency($depB);
// 3. Adding the third dependency should throw an exception or be handled
expect(fn() => $this->dependencyManager->addDependency($depC))
->toThrow(\InvalidArgumentException::class);
});
test('conditional dependencies', function () {
// 1. Create jobs
$job1 = createTestJob('cond-job-1', 'Conditional Job 1');
$job2 = createTestJob('cond-job-2', 'Conditional Job 2');
// 2. Create success-based dependency
$successDep = JobDependency::success('cond-job-2', 'cond-job-1');
$this->dependencyManager->addDependency($successDep);
// 3. Test that job2 is not ready when job1 failed
$failureMetrics = new JobMetrics(
jobId: 'cond-job-1',
queueName: 'default',
status: 'failed',
attempts: 3,
maxAttempts: 3,
executionTimeMs: 100.0,
memoryUsageBytes: 1024,
errorMessage: 'Test failure',
createdAt: date('Y-m-d H:i:s'),
startedAt: date('Y-m-d H:i:s'),
completedAt: null,
failedAt: date('Y-m-d H:i:s'),
metadata: []
);
$this->metricsManager->recordJobMetrics($failureMetrics);
// 4. Check that dependent job is not ready
$readyJobs = $this->resolutionEngine->getJobsReadyForExecution();
expect($readyJobs)->not()->toContain('cond-job-2');
});
test('queue metrics calculation', function () {
// 1. Create multiple job metrics
$metrics = [
new JobMetrics(
jobId: 'metric-job-1',
queueName: 'metrics-queue',
status: 'completed',
attempts: 1,
maxAttempts: 3,
executionTimeMs: 100.0,
memoryUsageBytes: 1024 * 1024,
errorMessage: null,
createdAt: date('Y-m-d H:i:s'),
startedAt: date('Y-m-d H:i:s'),
completedAt: date('Y-m-d H:i:s'),
failedAt: null,
metadata: []
),
new JobMetrics(
jobId: 'metric-job-2',
queueName: 'metrics-queue',
status: 'completed',
attempts: 1,
maxAttempts: 3,
executionTimeMs: 200.0,
memoryUsageBytes: 2 * 1024 * 1024,
errorMessage: null,
createdAt: date('Y-m-d H:i:s'),
startedAt: date('Y-m-d H:i:s'),
completedAt: date('Y-m-d H:i:s'),
failedAt: null,
metadata: []
),
new JobMetrics(
jobId: 'metric-job-3',
queueName: 'metrics-queue',
status: 'failed',
attempts: 3,
maxAttempts: 3,
executionTimeMs: 50.0,
memoryUsageBytes: 512 * 1024,
errorMessage: 'Test failure',
createdAt: date('Y-m-d H:i:s'),
startedAt: date('Y-m-d H:i:s'),
completedAt: null,
failedAt: date('Y-m-d H:i:s'),
metadata: []
)
];
// 2. Record all metrics
foreach ($metrics as $metric) {
$this->metricsManager->recordJobMetrics($metric);
}
// 3. Calculate queue metrics
$queueMetrics = $this->metricsManager->getQueueMetrics('metrics-queue', '1 hour');
// 4. Verify calculations
expect($queueMetrics->queueName)->toBe('metrics-queue')
->and($queueMetrics->totalJobs)->toBe(3)
->and($queueMetrics->completedJobs)->toBe(2)
->and($queueMetrics->failedJobs)->toBe(1)
->and($queueMetrics->successRate->toFloat())->toBe(66.67);
});
test('dead letter queue functionality', function () {
// 1. Create a job that exceeds max attempts
$deadLetterMetrics = new JobMetrics(
jobId: 'dead-letter-job',
queueName: 'default',
status: 'failed',
attempts: 3,
maxAttempts: 3,
executionTimeMs: 25.0,
memoryUsageBytes: 256 * 1024,
errorMessage: 'Max attempts exceeded',
createdAt: date('Y-m-d H:i:s'),
startedAt: date('Y-m-d H:i:s'),
completedAt: null,
failedAt: date('Y-m-d H:i:s'),
metadata: ['dead_letter' => true]
);
// 2. Record metrics
$this->metricsManager->recordJobMetrics($deadLetterMetrics);
// 3. Verify dead letter detection
$failedJobs = $this->metricsManager->getFailedJobs('default', '1 hour');
$deadLetterJob = array_filter($failedJobs, fn($job) => $job->jobId === 'dead-letter-job')[0] ?? null;
expect($deadLetterJob)->not()->toBeNull()
->and($deadLetterJob->attempts)->toBe(3)
->and($deadLetterJob->maxAttempts)->toBe(3)
->and($deadLetterJob->status)->toBe('failed');
});
test('system health monitoring', function () {
// 1. Create mixed job metrics for health calculation
$healthMetrics = [
// Healthy jobs
new JobMetrics('health-1', 'health-queue', 'completed', 1, 3, 50.0, 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
new JobMetrics('health-2', 'health-queue', 'completed', 1, 3, 75.0, 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
new JobMetrics('health-3', 'health-queue', 'completed', 1, 3, 100.0, 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
// One failed job
new JobMetrics('health-4', 'health-queue', 'failed', 2, 3, 25.0, 1024, 'Health test failure', date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, date('Y-m-d H:i:s'), [])
];
// 2. Record all metrics
foreach ($healthMetrics as $metric) {
$this->metricsManager->recordJobMetrics($metric);
}
// 3. Get system overview
$overview = $this->metricsManager->getSystemOverview();
// 4. Verify system health calculation
expect($overview)->toHaveKey('system_health_score')
->and($overview['total_jobs'])->toBeGreaterThan(0)
->and($overview['overall_success_rate'])->toBeGreaterThan(0);
});
test('performance and throughput metrics', function () {
// 1. Create performance test metrics with varying execution times
$performanceMetrics = [
new JobMetrics('perf-1', 'perf-queue', 'completed', 1, 3, 50.0, 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
new JobMetrics('perf-2', 'perf-queue', 'completed', 1, 3, 150.0, 2 * 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
new JobMetrics('perf-3', 'perf-queue', 'completed', 1, 3, 300.0, 4 * 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, [])
];
// 2. Record performance metrics
foreach ($performanceMetrics as $metric) {
$this->metricsManager->recordJobMetrics($metric);
}
// 3. Get performance statistics
$performanceStats = $this->metricsManager->getPerformanceStats('perf-queue', '1 hour');
// 4. Verify performance calculations
expect($performanceStats)->toHaveKey('average_execution_time_ms')
->and($performanceStats['average_execution_time_ms'])->toBe(166.67)
->and($performanceStats)->toHaveKey('average_memory_usage_mb')
->and($performanceStats['total_jobs'])->toBe(3);
// 5. Get throughput statistics
$throughputStats = $this->metricsManager->getThroughputStats('perf-queue', '1 hour');
// 6. Verify throughput calculations
expect($throughputStats)->toHaveKey('total_completed')
->and($throughputStats['total_completed'])->toBe(3)
->and($throughputStats)->toHaveKey('average_throughput_per_hour');
});