Skip to content

Commit 87c18fa

Browse files
committed
fix: test isolation for queue backends
1 parent 52a9fc6 commit 87c18fa

6 files changed

Lines changed: 223 additions & 165 deletions

File tree

src/Handlers/RabbitMQHandler.php

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ class RabbitMQHandler extends BaseHandler
3535
{
3636
private readonly AbstractConnection $connection;
3737
private readonly AMQPChannel $channel;
38-
private array $declaredQueues = [];
39-
private array $declaredExchanges = [];
38+
private array $declaredLogicalQueues = [];
39+
private array $declaredQueues = [];
40+
private array $declaredExchanges = [];
4041

4142
public function __construct(protected QueueConfig $config)
4243
{
@@ -273,8 +274,12 @@ public function clear(?string $queue = null): bool
273274
{
274275
try {
275276
if ($queue === null) {
276-
// Clear all configured queues
277-
foreach (array_keys($this->config->queuePriorities) as $queueName) {
277+
$queueNames = array_unique([
278+
...array_keys($this->config->queuePriorities),
279+
...array_keys($this->declaredLogicalQueues),
280+
]);
281+
282+
foreach ($queueNames as $queueName) {
278283
$this->clearQueue($queueName);
279284
}
280285
} else {
@@ -300,7 +305,8 @@ public function clear(?string $queue = null): bool
300305
*/
301306
private function declareQueue(string $queue): void
302307
{
303-
$priorities = $this->config->queuePriorities[$queue] ?? ['default'];
308+
$this->declaredLogicalQueues[$queue] = true;
309+
$priorities = $this->config->queuePriorities[$queue] ?? ['default'];
304310

305311
foreach ($priorities as $priority) {
306312
$queueName = $this->getQueueName($queue, $priority);
@@ -459,16 +465,20 @@ private function publishWithOptionalConfirm(AMQPMessage $message, string $exchan
459465
*/
460466
private function clearQueue(string $queue): void
461467
{
468+
// Purging a queue that does not exist closes the AMQP channel.
469+
$this->declareQueue($queue);
470+
462471
$priorities = $this->config->queuePriorities[$queue] ?? ['default'];
463472

464473
foreach ($priorities as $priority) {
465474
$queueName = $this->getQueueName($queue, $priority);
466475

467-
try {
468-
$this->channel->queue_purge($queueName);
469-
} catch (Throwable) {
470-
// Queue might not exist, ignore
471-
}
476+
$this->channel->queue_purge($queueName);
477+
}
478+
479+
$delayQueueName = $this->getDelayQueueName($queue);
480+
if (isset($this->declaredQueues[$delayQueueName])) {
481+
$this->channel->queue_purge($delayQueueName);
472482
}
473483
}
474484

tests/PushAndPopWithDelayTest.php

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -44,43 +44,47 @@ public function testPushAndPopWithDelay(string $name, string $class): void
4444
Time::setTestNow('2023-12-29 14:15:16');
4545

4646
$handler = new $class($this->config);
47-
$result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key1' => 'value1']);
48-
49-
$this->assertNotNull($result);
50-
51-
$result = $handler->push('queue-delay', 'success', ['key2' => 'value2']);
52-
53-
$this->assertNotNull($result);
54-
55-
if ($name === 'database') {
56-
$this->seeInDatabase('queue_jobs', [
57-
'queue' => 'queue-delay',
58-
'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]),
59-
'available_at' => 1703859376,
60-
]);
61-
62-
$this->seeInDatabase('queue_jobs', [
63-
'queue' => 'queue-delay',
64-
'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]),
65-
'available_at' => 1703859316,
66-
]);
47+
$handler->clear('queue-delay');
48+
49+
try {
50+
$result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key1' => 'value1']);
51+
$this->assertTrue($result->getStatus());
52+
53+
$result = $handler->push('queue-delay', 'success', ['key2' => 'value2']);
54+
$this->assertTrue($result->getStatus());
55+
56+
if ($name === 'database') {
57+
$this->seeInDatabase('queue_jobs', [
58+
'queue' => 'queue-delay',
59+
'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]),
60+
'available_at' => 1703859376,
61+
]);
62+
63+
$this->seeInDatabase('queue_jobs', [
64+
'queue' => 'queue-delay',
65+
'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]),
66+
'available_at' => 1703859316,
67+
]);
68+
}
69+
70+
$result = $handler->pop('queue-delay', ['default']);
71+
$this->assertInstanceOf(QueueJob::class, $result);
72+
$payload = ['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []];
73+
$this->assertSame($payload, $result->payload);
74+
75+
$result = $handler->pop('queue-delay', ['default']);
76+
$this->assertNull($result);
77+
78+
// add 1 minute
79+
Time::setTestNow('2023-12-29 14:16:16');
80+
81+
$result = $handler->pop('queue-delay', ['default']);
82+
$this->assertInstanceOf(QueueJob::class, $result);
83+
$payload = ['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []];
84+
$this->assertSame($payload, $result->payload);
85+
} finally {
86+
$handler->clear('queue-delay');
6787
}
68-
69-
$result = $handler->pop('queue-delay', ['default']);
70-
$this->assertInstanceOf(QueueJob::class, $result);
71-
$payload = ['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []];
72-
$this->assertSame($payload, $result->payload);
73-
74-
$result = $handler->pop('queue-delay', ['default']);
75-
$this->assertNull($result);
76-
77-
// add 1 minute
78-
Time::setTestNow('2023-12-29 14:16:16');
79-
80-
$result = $handler->pop('queue-delay', ['default']);
81-
$this->assertInstanceOf(QueueJob::class, $result);
82-
$payload = ['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []];
83-
$this->assertSame($payload, $result->payload);
8488
}
8589

8690
public static function providePushAndPopWithDelay(): iterable

tests/RabbitMQDelayTest.php

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use CodeIgniter\Queue\Entities\QueueJob;
1818
use CodeIgniter\Queue\Handlers\RabbitMQHandler;
1919
use CodeIgniter\Queue\QueuePushResult;
20+
use CodeIgniter\Test\ReflectionHelper;
2021
use PhpAmqpLib\Connection\AMQPConnectionFactory;
2122
use Tests\Support\Config\Queue as QueueConfig;
2223
use Tests\Support\TestCase;
@@ -29,13 +30,17 @@
2930
*/
3031
final class RabbitMQDelayTest extends TestCase
3132
{
33+
use ReflectionHelper;
34+
3235
private ?RabbitMQHandler $handler = null;
36+
private string $queue;
3337

3438
protected function setUp(): void
3539
{
3640
parent::setUp();
3741

38-
$config = config(QueueConfig::class);
42+
$config = config(QueueConfig::class);
43+
$this->queue = 'delay-test-queue-' . bin2hex(random_bytes(6));
3944

4045
// Skip tests if RabbitMQ is not available
4146
if (! $this->isRabbitMQAvailable()) {
@@ -52,9 +57,8 @@ protected function setUp(): void
5257
protected function tearDown(): void
5358
{
5459
if ($this->handler !== null) {
55-
// Clear test queues
5660
try {
57-
$this->handler->clear('delay-test-queue');
61+
$this->deleteDeclaredResources();
5862
} catch (Throwable) {
5963
// Ignore cleanup errors
6064
}
@@ -70,31 +74,31 @@ public function testDelayedMessageWithRealTiming(): void
7074
$startTime = time();
7175

7276
// Push a delayed job
73-
$result = $this->handler->setDelay($delaySeconds)->push('delay-test-queue', 'success', ['type' => 'delayed']);
77+
$result = $this->handler->setDelay($delaySeconds)->push($this->queue, 'success', ['type' => 'delayed']);
7478
$this->assertInstanceOf(QueuePushResult::class, $result);
7579
$this->assertTrue($result->getStatus());
7680

7781
// Push an immediate job
78-
$result = $this->handler->push('delay-test-queue', 'success', ['type' => 'immediate']);
82+
$result = $this->handler->push($this->queue, 'success', ['type' => 'immediate']);
7983
$this->assertInstanceOf(QueuePushResult::class, $result);
8084
$this->assertTrue($result->getStatus());
8185

8286
// Should get immediate job first
83-
$job = $this->handler->pop('delay-test-queue', ['default']);
87+
$job = $this->handler->pop($this->queue, ['default']);
8488
$this->assertInstanceOf(QueueJob::class, $job);
8589
$this->assertSame('immediate', $job->payload['data']['type']);
8690
$this->handler->done($job);
8791

8892
// Should not get delayed job yet (within first second)
89-
$job = $this->handler->pop('delay-test-queue', ['default']);
93+
$job = $this->handler->pop($this->queue, ['default']);
9094
$this->assertNull($job);
9195

9296
// Wait for delay to expire (with a small buffer)
9397
$waitTime = $delaySeconds + 1;
9498
sleep($waitTime);
9599

96100
// Should now get the delayed job
97-
$job = $this->handler->pop('delay-test-queue', ['default']);
101+
$job = $this->handler->pop($this->queue, ['default']);
98102
$this->assertInstanceOf(QueueJob::class, $job);
99103
$this->assertSame('delayed', $job->payload['data']['type']);
100104

@@ -109,34 +113,34 @@ public function testDelayedMessageWithRealTiming(): void
109113
public function testMultipleDelayedJobsWithDifferentDelays(): void
110114
{
111115
// Push jobs with different delays
112-
$result1 = $this->handler->setDelay(1)->push('delay-test-queue', 'success', ['order' => 'first', 'delay' => 1]);
113-
$result2 = $this->handler->setDelay(3)->push('delay-test-queue', 'success', ['order' => 'second', 'delay' => 3]);
114-
$result3 = $this->handler->push('delay-test-queue', 'success', ['order' => 'immediate', 'delay' => 0]);
116+
$result1 = $this->handler->setDelay(1)->push($this->queue, 'success', ['order' => 'first', 'delay' => 1]);
117+
$result2 = $this->handler->setDelay(3)->push($this->queue, 'success', ['order' => 'second', 'delay' => 3]);
118+
$result3 = $this->handler->push($this->queue, 'success', ['order' => 'immediate', 'delay' => 0]);
115119

116120
$this->assertTrue($result1->getStatus());
117121
$this->assertTrue($result2->getStatus());
118122
$this->assertTrue($result3->getStatus());
119123

120124
// Should get immediate job first
121-
$job = $this->handler->pop('delay-test-queue', ['default']);
125+
$job = $this->handler->pop($this->queue, ['default']);
122126
$this->assertInstanceOf(QueueJob::class, $job);
123127
$this->assertSame('immediate', $job->payload['data']['order']);
124128
$this->handler->done($job);
125129

126130
// Wait 2 seconds - should get first delayed job
127131
sleep(2);
128-
$job = $this->handler->pop('delay-test-queue', ['default']);
132+
$job = $this->handler->pop($this->queue, ['default']);
129133
$this->assertInstanceOf(QueueJob::class, $job);
130134
$this->assertSame('first', $job->payload['data']['order']);
131135
$this->handler->done($job);
132136

133137
// Should not get second job yet
134-
$job = $this->handler->pop('delay-test-queue', ['default']);
138+
$job = $this->handler->pop($this->queue, ['default']);
135139
$this->assertNull($job);
136140

137141
// Wait another 2 seconds - should get second delayed job
138142
sleep(2);
139-
$job = $this->handler->pop('delay-test-queue', ['default']);
143+
$job = $this->handler->pop($this->queue, ['default']);
140144
$this->assertInstanceOf(QueueJob::class, $job);
141145
$this->assertSame('second', $job->payload['data']['order']);
142146
$this->handler->done($job);
@@ -145,11 +149,11 @@ public function testMultipleDelayedJobsWithDifferentDelays(): void
145149
public function testZeroDelayWorksImmediately(): void
146150
{
147151
// Jobs with 0 delay should work immediately
148-
$result = $this->handler->setDelay(0)->push('delay-test-queue', 'success', ['type' => 'zero-delay']);
152+
$result = $this->handler->setDelay(0)->push($this->queue, 'success', ['type' => 'zero-delay']);
149153
$this->assertTrue($result->getStatus());
150154

151155
// Should be able to pop immediately
152-
$job = $this->handler->pop('delay-test-queue', ['default']);
156+
$job = $this->handler->pop($this->queue, ['default']);
153157
$this->assertInstanceOf(QueueJob::class, $job);
154158
$this->assertSame('zero-delay', $job->payload['data']['type']);
155159

@@ -163,4 +167,17 @@ private function isRabbitMQAvailable(): bool
163167
{
164168
return class_exists(AMQPConnectionFactory::class);
165169
}
170+
171+
private function deleteDeclaredResources(): void
172+
{
173+
$channel = self::getPrivateProperty($this->handler, 'channel');
174+
175+
foreach (array_keys(self::getPrivateProperty($this->handler, 'declaredQueues')) as $queue) {
176+
$channel->queue_delete($queue);
177+
}
178+
179+
foreach (array_keys(self::getPrivateProperty($this->handler, 'declaredExchanges')) as $exchange) {
180+
$channel->exchange_delete($exchange);
181+
}
182+
}
166183
}

0 commit comments

Comments
 (0)