id} executed"; } } class PriorityTestJob { public function __construct(public string $priority) { } public function handle(): string { return "job with {$this->priority} priority executed"; } } describe('Queue Interface Basic Operations', function () { beforeEach(function () { $this->queue = new InMemoryQueue(); $this->testJob = new SimpleTestJob(); }); 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 = (object)['id' => 1]; $job2 = (object)['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'] ?? 0)->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((object)['type' => 'low'], QueuePriority::low()); $jobs['deferred'] = JobPayload::create((object)['type' => 'deferred'], QueuePriority::deferred()); $jobs['normal'] = JobPayload::create((object)['type' => 'normal'], QueuePriority::normal()); $jobs['high'] = JobPayload::create((object)['type' => 'high'], QueuePriority::high()); $jobs['critical'] = JobPayload::create((object)['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((object)['id' => 'custom_high'], new QueuePriority(500)); $customLow = JobPayload::create((object)['id' => 'custom_low'], new QueuePriority(-50)); $standardHigh = JobPayload::create((object)['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 = (object)['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 CounterTestJob($i); $payload = JobPayload::create($job, 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 }); });