Files
michaelschiemer/tests/Framework/Queue/QueueTest.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

365 lines
13 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Queue\InMemoryQueue;
use App\Framework\Queue\ValueObjects\JobPayload;
use App\Framework\Queue\ValueObjects\QueuePriority;
describe('Queue Interface Basic Operations', function () {
beforeEach(function () {
$this->queue = new InMemoryQueue();
$this->testJob = new class () {
public function handle(): string
{
return 'test job executed';
}
};
});
describe('push() operation', function () {
it('can push jobs to queue', function () {
$payload = JobPayload::create($this->testJob);
$this->queue->push($payload);
expect($this->queue->size())->toBe(1);
});
it('maintains priority order when pushing multiple jobs', function () {
$lowPriorityJob = JobPayload::create($this->testJob, QueuePriority::low());
$highPriorityJob = JobPayload::create($this->testJob, QueuePriority::high());
$criticalJob = JobPayload::create($this->testJob, QueuePriority::critical());
// Push in random order
$this->queue->push($lowPriorityJob);
$this->queue->push($criticalJob);
$this->queue->push($highPriorityJob);
expect($this->queue->size())->toBe(3);
// Peek should return critical priority first
$next = $this->queue->peek();
expect($next->priority->isCritical())->toBeTrue();
});
it('accepts jobs with different configurations', function () {
$immediateJob = JobPayload::immediate($this->testJob);
$delayedJob = JobPayload::delayed($this->testJob, Duration::fromSeconds(30));
$backgroundJob = JobPayload::background($this->testJob);
$criticalJob = JobPayload::critical($this->testJob);
$this->queue->push($immediateJob);
$this->queue->push($delayedJob);
$this->queue->push($backgroundJob);
$this->queue->push($criticalJob);
expect($this->queue->size())->toBe(4);
});
});
describe('pop() operation', function () {
it('returns null when queue is empty', function () {
expect($this->queue->pop())->toBeNull();
});
it('returns and removes highest priority job first', function () {
$lowJob = JobPayload::create($this->testJob, QueuePriority::low());
$highJob = JobPayload::create($this->testJob, QueuePriority::high());
$this->queue->push($lowJob);
$this->queue->push($highJob);
$popped = $this->queue->pop();
expect($popped->priority->isHigh())->toBeTrue();
expect($this->queue->size())->toBe(1);
$remaining = $this->queue->pop();
expect($remaining->priority->isLow())->toBeTrue();
expect($this->queue->size())->toBe(0);
});
it('processes FIFO for same priority jobs', function () {
$job1 = new class () {
public $id = 1;
};
$job2 = new class () {
public $id = 2;
};
$payload1 = JobPayload::create($job1, QueuePriority::normal());
$payload2 = JobPayload::create($job2, QueuePriority::normal());
$this->queue->push($payload1);
$this->queue->push($payload2);
$first = $this->queue->pop();
expect($first->job->id)->toBe(1);
$second = $this->queue->pop();
expect($second->job->id)->toBe(2);
});
});
describe('peek() operation', function () {
it('returns null when queue is empty', function () {
expect($this->queue->peek())->toBeNull();
});
it('returns next job without removing it', function () {
$payload = JobPayload::create($this->testJob);
$this->queue->push($payload);
$peeked = $this->queue->peek();
expect($peeked)->not->toBeNull();
expect($this->queue->size())->toBe(1);
// Should return same job when peeked again
$peekedAgain = $this->queue->peek();
expect($peekedAgain)->toBe($peeked);
});
it('shows highest priority job', function () {
$normalJob = JobPayload::create($this->testJob, QueuePriority::normal());
$criticalJob = JobPayload::create($this->testJob, QueuePriority::critical());
$this->queue->push($normalJob);
$this->queue->push($criticalJob);
$peeked = $this->queue->peek();
expect($peeked->priority->isCritical())->toBeTrue();
});
});
describe('size() operation', function () {
it('returns 0 for empty queue', function () {
expect($this->queue->size())->toBe(0);
});
it('tracks size correctly as jobs are added and removed', function () {
expect($this->queue->size())->toBe(0);
$this->queue->push(JobPayload::create($this->testJob));
expect($this->queue->size())->toBe(1);
$this->queue->push(JobPayload::create($this->testJob));
expect($this->queue->size())->toBe(2);
$this->queue->pop();
expect($this->queue->size())->toBe(1);
$this->queue->pop();
expect($this->queue->size())->toBe(0);
});
});
describe('clear() operation', function () {
it('returns 0 when clearing empty queue', function () {
expect($this->queue->clear())->toBe(0);
});
it('removes all jobs and returns count', function () {
$this->queue->push(JobPayload::create($this->testJob));
$this->queue->push(JobPayload::create($this->testJob));
$this->queue->push(JobPayload::create($this->testJob));
expect($this->queue->size())->toBe(3);
$cleared = $this->queue->clear();
expect($cleared)->toBe(3);
expect($this->queue->size())->toBe(0);
});
it('queue is usable after clearing', function () {
$this->queue->push(JobPayload::create($this->testJob));
$this->queue->clear();
// Should be able to add new jobs
$this->queue->push(JobPayload::create($this->testJob));
expect($this->queue->size())->toBe(1);
});
});
describe('getStats() operation', function () {
it('returns basic stats for empty queue', function () {
$stats = $this->queue->getStats();
expect($stats)->toHaveKey('size');
expect($stats['size'])->toBe(0);
expect($stats)->toHaveKey('priority_breakdown');
expect($stats['priority_breakdown'])->toBe([]);
});
it('provides priority breakdown for populated queue', function () {
$this->queue->push(JobPayload::create($this->testJob, QueuePriority::high()));
$this->queue->push(JobPayload::create($this->testJob, QueuePriority::high()));
$this->queue->push(JobPayload::create($this->testJob, QueuePriority::normal()));
$this->queue->push(JobPayload::create($this->testJob, QueuePriority::low()));
$stats = $this->queue->getStats();
expect($stats['size'])->toBe(4);
expect($stats['priority_breakdown']['high'])->toBe(2);
expect($stats['priority_breakdown']['normal'])->toBe(1);
expect($stats['priority_breakdown']['low'])->toBe(1);
});
it('updates stats as queue changes', function () {
$this->queue->push(JobPayload::create($this->testJob, QueuePriority::critical()));
$this->queue->push(JobPayload::create($this->testJob, QueuePriority::normal()));
$stats = $this->queue->getStats();
expect($stats['size'])->toBe(2);
expect($stats['priority_breakdown']['critical'])->toBe(1);
// Remove one job
$this->queue->pop();
$updatedStats = $this->queue->getStats();
expect($updatedStats['size'])->toBe(1);
expect($updatedStats['priority_breakdown']['critical'])->toBe(0);
expect($updatedStats['priority_breakdown']['normal'])->toBe(1);
});
});
});
describe('Queue Priority Processing', function () {
beforeEach(function () {
$this->queue = new InMemoryQueue();
});
it('processes jobs in correct priority order', function () {
$jobs = [];
// Create jobs with different priorities
$jobs['low'] = JobPayload::create(new class () {
public $type = 'low';
}, QueuePriority::low());
$jobs['deferred'] = JobPayload::create(new class () {
public $type = 'deferred';
}, QueuePriority::deferred());
$jobs['normal'] = JobPayload::create(new class () {
public $type = 'normal';
}, QueuePriority::normal());
$jobs['high'] = JobPayload::create(new class () {
public $type = 'high';
}, QueuePriority::high());
$jobs['critical'] = JobPayload::create(new class () {
public $type = 'critical';
}, QueuePriority::critical());
// Push in random order
$this->queue->push($jobs['normal']);
$this->queue->push($jobs['deferred']);
$this->queue->push($jobs['critical']);
$this->queue->push($jobs['low']);
$this->queue->push($jobs['high']);
// Pop should return in priority order
$order = [];
while (($job = $this->queue->pop()) !== null) {
$order[] = $job->job->type;
}
expect($order)->toBe(['critical', 'high', 'normal', 'low', 'deferred']);
});
it('handles custom priority values correctly', function () {
$customHigh = JobPayload::create(new class () {
public $id = 'custom_high';
}, new QueuePriority(500));
$customLow = JobPayload::create(new class () {
public $id = 'custom_low';
}, new QueuePriority(-50));
$standardHigh = JobPayload::create(new class () {
public $id = 'standard_high';
}, QueuePriority::high());
$this->queue->push($customLow);
$this->queue->push($standardHigh);
$this->queue->push($customHigh);
$first = $this->queue->pop();
expect($first->job->id)->toBe('custom_high'); // 500 priority
$second = $this->queue->pop();
expect($second->job->id)->toBe('standard_high'); // 100 priority
$third = $this->queue->pop();
expect($third->job->id)->toBe('custom_low'); // -50 priority
});
});
describe('Queue Edge Cases', function () {
beforeEach(function () {
$this->queue = new InMemoryQueue();
});
it('handles many operations on empty queue gracefully', function () {
expect($this->queue->pop())->toBeNull();
expect($this->queue->pop())->toBeNull();
expect($this->queue->peek())->toBeNull();
expect($this->queue->peek())->toBeNull();
expect($this->queue->size())->toBe(0);
expect($this->queue->clear())->toBe(0);
expect($this->queue->clear())->toBe(0);
});
it('maintains integrity after mixed operations', function () {
$job = new class () {
public $data = 'test';
};
// Complex sequence of operations
$this->queue->push(JobPayload::create($job));
expect($this->queue->size())->toBe(1);
$peeked = $this->queue->peek();
expect($peeked->job->data)->toBe('test');
expect($this->queue->size())->toBe(1);
$popped = $this->queue->pop();
expect($popped->job->data)->toBe('test');
expect($this->queue->size())->toBe(0);
expect($this->queue->peek())->toBeNull();
expect($this->queue->pop())->toBeNull();
// Add more after emptying
$this->queue->push(JobPayload::create($job));
expect($this->queue->size())->toBe(1);
});
it('handles large number of jobs efficiently', function () {
$start = microtime(true);
// Add 1000 jobs
for ($i = 0; $i < 1000; $i++) {
$job = new class () {
public function __construct(public int $id)
{
}
};
$payload = JobPayload::create(new $job($i), QueuePriority::normal());
$this->queue->push($payload);
}
expect($this->queue->size())->toBe(1000);
// Process all jobs
$processed = 0;
while ($this->queue->pop() !== null) {
$processed++;
}
expect($processed)->toBe(1000);
expect($this->queue->size())->toBe(0);
$elapsed = microtime(true) - $start;
expect($elapsed)->toBeLessThan(1.0); // Should complete within 1 second
});
});