* * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Tests; use CodeIgniter\I18n\Time; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Exceptions\QueueException; use CodeIgniter\Queue\Handlers\PredisHandler; use CodeIgniter\Test\ReflectionHelper; use Exception; use ReflectionException; use Tests\Support\Config\Queue as QueueConfig; use Tests\Support\Database\Seeds\TestRedisQueueSeeder; use Tests\Support\TestCase; /** * @internal */ final class PredisHandlerTest extends TestCase { use ReflectionHelper; protected $seed = TestRedisQueueSeeder::class; private QueueConfig $config; protected function setUp(): void { parent::setUp(); $this->config = config(QueueConfig::class); } public function testPredisHandler(): void { $handler = new PredisHandler($this->config); $this->assertInstanceOf(PredisHandler::class, $handler); } public function testPriority(): void { $handler = new PredisHandler($this->config); $handler->setPriority('high'); $this->assertSame('high', self::getPrivateProperty($handler, 'priority')); } public function testPriorityException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The priority name should consists only lowercase letters.'); $handler = new PredisHandler($this->config); $handler->setPriority('high_:'); } /** * @throws ReflectionException */ public function testPush(): void { $handler = new PredisHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue:low')); $task = $predis->zrangebyscore('queues:queue:low', '-inf', Time::now()->timestamp, ['limit' => [0, 1]]); $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key' => 'value'], $queueJob->payload['data']); $this->assertSame([], $queueJob->payload['metadata']); } /** * @throws ReflectionException */ public function testPushWithPriority(): void { $handler = new PredisHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue:high')); $task = $predis->zrangebyscore('queues:queue:high', '-inf', Time::now()->timestamp, ['limit' => [0, 1]]); $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key' => 'value'], $queueJob->payload['data']); $this->assertSame([], $queueJob->payload['metadata']); } /** * @throws ReflectionException */ public function testPushWithDelay(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new PredisHandler($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue-delay:default')); $task = $predis->zrangebyscore('queues:queue-delay:default', '-inf', Time::now()->addSeconds(MINUTE)->timestamp, ['limit' => [0, 1]]); $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key' => 'value'], $queueJob->payload['data']); $this->assertSame([], $queueJob->payload['metadata']); } /** * @throws Exception */ public function testChain(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new PredisHandler($this->config); $result = $handler->chain(static function ($chain): void { $chain ->push('queue', 'success', ['key1' => 'value1']) ->push('queue', 'success', ['key2' => 'value2']); }); $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue:low')); $task = $predis->zrangebyscore('queues:queue:low', '-inf', Time::now()->timestamp, ['limit' => [0, 1]]); $job = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $job->payload['job']); $this->assertSame(['key1' => 'value1'], $job->payload['data']); $this->assertArrayHasKey('metadata', $job->payload); $this->assertArrayHasKey('queue', $job->payload['metadata']); $this->assertSame('queue', $job->payload['metadata']['queue']); $this->assertArrayHasKey('chainedJobs', $job->payload['metadata']); $chainedJobs = $job->payload['metadata']['chainedJobs']; $this->assertCount(1, $chainedJobs); $this->assertSame('success', $chainedJobs[0]['job']); $this->assertSame(['key2' => 'value2'], $chainedJobs[0]['data']); $this->assertSame('queue', $chainedJobs[0]['metadata']['queue']); } /** * @throws Exception */ public function testChainWithPriorityAndDelay(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new PredisHandler($this->config); $result = $handler->chain(static function ($chain): void { $chain ->push('queue', 'success', ['key1' => 'value1']) ->setPriority('high') ->setDelay(60) ->push('queue', 'success', ['key2' => 'value2']) ->setPriority('low') ->setDelay(120); }); $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); // Should be in high priority queue $this->assertSame(1, $predis->zcard('queues:queue:high')); // Check with delay $task = $predis->zrangebyscore('queues:queue:high', '-inf', Time::now()->addSeconds(61)->timestamp, ['limit' => [0, 1]]); $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key1' => 'value1'], $queueJob->payload['data']); $this->assertArrayHasKey('metadata', $queueJob->payload); // Check metadata $meta = $queueJob->payload['metadata']; $this->assertSame('queue', $meta['queue']); $this->assertSame('high', $meta['priority']); $this->assertSame(60, $meta['delay']); // Check a chained job with its priority and delay $this->assertArrayHasKey('chainedJobs', $meta); $chainedJobs = $meta['chainedJobs']; $this->assertCount(1, $chainedJobs); $this->assertSame('success', $chainedJobs[0]['job']); $this->assertSame(['key2' => 'value2'], $chainedJobs[0]['data']); $this->assertSame('queue', $chainedJobs[0]['metadata']['queue']); $this->assertSame('low', $chainedJobs[0]['metadata']['priority']); $this->assertSame(120, $chainedJobs[0]['metadata']['delay']); } public function testPushException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This job name is not defined in the $jobHandlers array.'); $handler = new PredisHandler($this->config); $handler->push('queue', 'not-exists', ['key' => 'value']); } public function testPushWithPriorityException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This queue has incorrectly defined priority: "invalid" for the queue: "queue".'); $handler = new PredisHandler($this->config); $handler->setPriority('invalid')->push('queue', 'success', ['key' => 'value']); } /** * @throws ReflectionException */ public function testPop(): void { $handler = new PredisHandler($this->config); $result = $handler->pop('queue1', ['default']); $this->assertInstanceOf(QueueJob::class, $result); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1_234_567_890_654_321, $result->id); $this->assertSame(0, $predis->zcard('queues:queue1:default')); $this->assertSame(1, $predis->hexists('queues:queue1::reserved', $result->id)); } public function testPopEmpty(): void { $handler = new PredisHandler($this->config); $result = $handler->pop('queue123', ['default']); $this->assertNull($result); } /** * @throws ReflectionException */ public function testLater(): void { $handler = new PredisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->hexists('queues:queue1::reserved', $queueJob->id)); $this->assertSame(0, $predis->zcard('queues:queue1:default')); $this->assertInstanceOf(QueueJob::class, $queueJob); $result = $handler->later($queueJob, 60); $this->assertTrue($result); $this->assertSame(0, $predis->hexists('queues:queue1::reserved', $queueJob->id)); $this->assertSame(1, $predis->zcard('queues:queue1:default')); } /** * @throws ReflectionException */ public function testFailedAndKeepJob(): void { $handler = new PredisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); $err = new Exception('Sample exception'); $this->assertInstanceOf(QueueJob::class, $queueJob); $result = $handler->failed($queueJob, $err, true); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertTrue($result); $this->assertSame(0, $predis->hexists('queues:queue1::reserved', $queueJob->id)); $this->assertSame(0, $predis->zcard('queues:queue1:default')); $this->seeInDatabase('queue_jobs_failed', [ 'id' => 2, 'connection' => 'predis', 'queue' => 'queue1', ]); } /** * @throws ReflectionException */ public function testFailedAndDontKeepJob(): void { $handler = new PredisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); $err = new Exception('Sample exception'); $this->assertInstanceOf(QueueJob::class, $queueJob); $result = $handler->failed($queueJob, $err, false); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertTrue($result); $this->assertSame(0, $predis->hexists('queues:queue1::reserved', $queueJob->id)); $this->assertSame(0, $predis->zcard('queues:queue1:default')); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 2, 'connection' => 'predis', 'queue' => 'queue1', ]); } /** * @throws ReflectionException */ public function testDone(): void { $handler = new PredisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(0, $predis->zcard('queues:queue1:default')); $this->assertInstanceOf(QueueJob::class, $queueJob); $result = $handler->done($queueJob); $this->assertTrue($result); $this->assertSame(0, $predis->hexists('queues:queue1::reserved', $queueJob->id)); } /** * @throws ReflectionException */ public function testClear(): void { $handler = new PredisHandler($this->config); $result = $handler->clear('queue1'); $this->assertTrue($result); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(0, $predis->zcard('queues:queue1:default')); $result = $handler->clear('queue1'); $this->assertTrue($result); } /** * @throws ReflectionException */ public function testClearAll(): void { $handler = new PredisHandler($this->config); $result = $handler->clear(); $this->assertTrue($result); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertCount(0, $predis->keys('queues:*')); $result = $handler->clear(); $this->assertTrue($result); } /** * @throws ReflectionException */ public function testRetry(): void { $handler = new PredisHandler($this->config); $count = $handler->retry(1, 'queue1'); $this->assertSame(1, $count); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(2, $predis->zcard('queues:queue1:default')); $task = $predis->zrangebyscore('queues:queue1:default', '-inf', Time::now()->timestamp, ['limit' => [0, 2]]); $queueJob = new QueueJob(json_decode((string) $task[1], true)); $this->assertSame('failure', $queueJob->payload['job']); $this->assertSame(['failed' => true], $queueJob->payload['data']); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 1, ]); } }