* * 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\Enums\Status; use CodeIgniter\Queue\Exceptions\QueueException; use CodeIgniter\Queue\Handlers\DatabaseHandler; use CodeIgniter\Queue\Models\QueueJobFailedModel; use CodeIgniter\Test\ReflectionHelper; use Exception; use ReflectionException; use Tests\Support\Config\Queue as QueueConfig; use Tests\Support\Database\Seeds\TestDatabaseQueueSeeder; use Tests\Support\TestCase; /** * @internal */ final class DatabaseHandlerTest extends TestCase { use ReflectionHelper; protected $seed = TestDatabaseQueueSeeder::class; private QueueConfig $config; protected function setUp(): void { parent::setUp(); $this->config = config(QueueConfig::class); } public function testDatabaseHandler(): void { $handler = new DatabaseHandler($this->config); $this->assertInstanceOf(DatabaseHandler::class, $handler); } public function testPriority(): void { $handler = new DatabaseHandler($this->config); $handler->setPriority('high'); $this->assertSame('high', self::getPrivateProperty($handler, 'priority')); } public function testPriorityNameException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The priority name should consists only lowercase letters.'); $handler = new DatabaseHandler($this->config); $handler->setPriority('high_:'); } public function testPriorityNameLengthException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The priority name is too long. It should be no longer than 64 letters.'); $handler = new DatabaseHandler($this->config); $handler->setPriority(str_repeat('a', 65)); } /** * @throws ReflectionException */ public function testPush(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), 'available_at' => 1703859316, ]); } /** * @throws ReflectionException */ public function testPushWithPriority(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new DatabaseHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), 'priority' => 'high', 'available_at' => 1703859316, ]); } /** * @throws ReflectionException */ public function testPushAndPopWithPriority(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key1' => 'value1']); $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]), 'priority' => 'low', 'available_at' => 1703859316, ]); $result = $handler->setPriority('high')->push('queue', 'success', ['key2' => 'value2']); $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]), 'priority' => 'high', 'available_at' => 1703859316, ]); $result = $handler->pop('queue', ['high', 'low']); $this->assertInstanceOf(QueueJob::class, $result); $payload = ['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]; $this->assertSame($payload, $result->payload); $result = $handler->pop('queue', ['high', 'low']); $this->assertInstanceOf(QueueJob::class, $result); $payload = ['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]; $this->assertSame($payload, $result->payload); } /** * @throws Exception */ public function testPushWithDelay(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new DatabaseHandler($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); $this->assertTrue($result->getStatus()); $availableAt = 1703859376; $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue-delay', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), 'available_at' => $availableAt, ]); $this->assertEqualsWithDelta(MINUTE, $availableAt - Time::now()->getTimestamp(), 1); } /** * @throws Exception */ public function testChain(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new DatabaseHandler($this->config); $result = $handler->chain(static function ($chain): void { $chain ->push('queue', 'success', ['key1' => 'value1']) ->push('queue', 'success', ['key2' => 'value2']); }); $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ 'job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => [ 'queue' => 'queue', 'chainedJobs' => [ [ 'job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => ['queue' => 'queue']], ], ], ]), 'available_at' => 1703859316, ]); } public function testChainWithPriorityAndDelay(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new DatabaseHandler($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()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ 'job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => [ 'queue' => 'queue', 'priority' => 'high', 'delay' => 60, 'chainedJobs' => [ [ 'job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => [ 'queue' => 'queue', 'priority' => 'low', 'delay' => 120, ], ], ], ], ]), 'available_at' => 1703859316 + 60, // Adding delay to available_at ]); } public function testPushWithDelayException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The number of seconds of delay must be a positive integer.'); $handler = new DatabaseHandler($this->config); $handler->setDelay(-60); } /** * @throws ReflectionException */ public function testPushException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This job name is not defined in the $jobHandlers array.'); $handler = new DatabaseHandler($this->config); $handler->push('queue', 'not-exists', ['key' => 'value']); } /** * @throws ReflectionException */ public function testPushWithPriorityException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This queue has incorrectly defined priority: "invalid" for the queue: "queue".'); $handler = new DatabaseHandler($this->config); $handler->setPriority('invalid')->push('queue', 'success', ['key' => 'value']); } /** * @throws ReflectionException */ public function testPushWithIncorrectQueueFormatException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The queue name should consists only lowercase letters or numbers.'); $handler = new DatabaseHandler($this->config); $handler->push('queue*', 'success', ['key' => 'value']); } /** * @throws ReflectionException */ public function testPushWithTooLongQueueNameException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The queue name is too long. It should be no longer than 64 letters.'); $handler = new DatabaseHandler($this->config); $handler->push(str_repeat('a', 65), 'success', ['key' => 'value']); } /** * @throws ReflectionException */ public function testPop(): void { $handler = new DatabaseHandler($this->config); $result = $handler->pop('queue1', ['default']); $this->assertInstanceOf(QueueJob::class, $result); $this->seeInDatabase('queue_jobs', [ 'status' => Status::RESERVED->value, 'available_at' => 1_697_269_860, ]); } /** * @throws ReflectionException */ public function testPopEmpty(): void { $handler = new DatabaseHandler($this->config); $result = $handler->pop('queue123', ['default']); $this->assertNull($result); } /** * @throws ReflectionException */ public function testLater(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); $this->seeInDatabase('queue_jobs', [ 'id' => 2, 'status' => Status::RESERVED->value, ]); $this->assertInstanceOf(QueueJob::class, $queueJob); $result = $handler->later($queueJob, 60); $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ 'id' => 2, 'status' => Status::PENDING->value, 'available_at' => Time::now()->addSeconds(60)->timestamp, ]); } /** * @throws ReflectionException */ public function testFailedAndKeepJob(): void { Time::setTestNow('2023-12-29 14:15:16'); $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); $err = new Exception('Sample exception'); $this->assertInstanceOf(QueueJob::class, $queueJob); $result = $handler->failed($queueJob, $err, true); $this->assertTrue($result); $this->dontSeeInDatabase('queue_jobs', [ 'id' => 2, ]); $this->seeInDatabase('queue_jobs_failed', [ 'id' => 2, 'connection' => 'database', 'queue' => 'queue1', 'failed_at' => 1703859316, ]); } public function testFailedAndDontKeepJob(): void { $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); $err = new Exception('Sample exception'); $this->assertInstanceOf(QueueJob::class, $queueJob); $result = $handler->failed($queueJob, $err, false); $this->assertTrue($result); $this->dontSeeInDatabase('queue_jobs', [ 'id' => 2, ]); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 2, 'connection' => 'database', 'queue' => 'queue1', ]); } /** * @throws ReflectionException */ public function testDone(): void { $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); $this->assertInstanceOf(QueueJob::class, $queueJob); $result = $handler->done($queueJob); $this->assertTrue($result); $this->dontSeeInDatabase('queue_jobs', [ 'id' => 2, ]); } public function testClear(): void { $handler = new DatabaseHandler($this->config); $result = $handler->clear('queue1'); $this->assertTrue($result); $this->dontSeeInDatabase('queue_jobs', [ 'id' => 1, ]); $this->dontSeeInDatabase('queue_jobs', [ 'id' => 2, ]); } public function testRetry(): void { $handler = new DatabaseHandler($this->config); $count = $handler->retry(1, 'queue1'); $this->assertSame(1, $count); $this->seeInDatabase('queue_jobs', [ 'id' => 3, 'queue' => 'queue1', 'payload' => json_encode(['job' => 'failure', 'data' => [], 'metadata' => []]), ]); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 1, ]); } public function testForget(): void { $handler = new DatabaseHandler($this->config); $result = $handler->forget(1); $this->assertTrue($result); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 1, ]); } public function testForgetFalse(): void { $handler = new DatabaseHandler($this->config); $result = $handler->forget(1111); $this->assertFalse($result); } /** * @throws ReflectionException */ public function testFlush(): void { $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); $err = new Exception('Sample exception here'); $this->assertInstanceOf(QueueJob::class, $queueJob); $result = $handler->failed($queueJob, $err, true); $this->assertTrue($result); // Set first record as older than 1 hour model(QueueJobFailedModel::class)->builder()->where('id', 1)->update(['failed_at' => 1_697_269_860]); $handler->flush(1, 'queue1'); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 1, ]); $this->seeInDatabase('queue_jobs_failed', [ 'id' => 2, ]); } public function testFlushAll(): void { $handler = new DatabaseHandler($this->config); $handler->flush(null, null); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 1, ]); } public function testListFailed(): void { $handler = new DatabaseHandler($this->config); $list = $handler->listFailed('queue1'); $this->assertCount(1, $list); } }