/
githubmirror
/
framework
Обзор
Документация
Войти
/
githubmirror
/
framework
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
13.x
tests/Queue/QueueRedisQueueTest.php
512 строк
21 KB
Jason McCreary
[13.x] Mockery cleanup (#61117)
10 авг 2026, 20:23
Не верифицирован
10 авг 2026, 20:23
2ee40ee
Код
Авторство
О чём код?
<?php namespace Illuminate\Tests\Queue; use Illuminate\Container\Container; use Illuminate\Contracts\Redis\Factory; use Illuminate\Queue\Attributes\Delay; use Illuminate\Queue\LuaScripts; use Illuminate\Queue\Queue; use Illuminate\Queue\RedisQueue; use Illuminate\Redis\Connections\PhpRedisClusterConnection; use Illuminate\Redis\Connections\PredisClusterConnection; use Illuminate\Support\Carbon; use Illuminate\Support\Str; use Mockery; use PHPUnit\Framework\TestCase; class QueueRedisQueueTest extends TestCase { public function testPushProperlyPushesJobOntoRedis() { $uuid = Str::uuid(); Str::createUuidsUsing(function () use ($uuid) { return $uuid; }); $time = Carbon::now(); Carbon::setTestNow($time); $redis = Mockery::mock(Factory::class); $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['getRandomId'])->setConstructorArgs([$redis, 'default'])->getMock(); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $container = Mockery::spy(Container::class); $queue->setContainer($container); $redis->shouldReceive('connection')->atLeast()->once()->andReturn($redis); $redis->expects('isCluster')->andReturn(false); $redis->expects('eval')->with(LuaScripts::push(), 2, 'queues:default', 'queues:default:notify', json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])); $id = $queue->push('foo', ['data']); $this->assertSame('foo', $id); $container->shouldHaveReceived('bound')->with('events')->twice(); Str::createUuidsNormally(); } public function testPushProperlyPushesJobOntoRedisWithCustomPayloadHook() { $uuid = Str::uuid(); Str::createUuidsUsing(function () use ($uuid) { return $uuid; }); $time = Carbon::now(); Carbon::setTestNow($time); $redis = Mockery::mock(Factory::class); $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['getRandomId'])->setConstructorArgs([$redis, 'default'])->getMock(); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $container = Mockery::spy(Container::class); $queue->setContainer($container); $redis->shouldReceive('connection')->atLeast()->once()->andReturn($redis); $redis->expects('isCluster')->andReturn(false); $redis->expects('eval')->with(LuaScripts::push(), 2, 'queues:default', 'queues:default:notify', json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'custom' => 'taylor', 'id' => 'foo', 'attempts' => 0, 'delay' => null])); Queue::createPayloadUsing(function ($connection, $queue, $payload) { return ['custom' => 'taylor']; }); $id = $queue->push('foo', ['data']); $this->assertSame('foo', $id); $container->shouldHaveReceived('bound')->with('events')->twice(); Queue::createPayloadUsing(null); Str::createUuidsNormally(); } public function testPushProperlyPushesJobOntoRedisWithTwoCustomPayloadHook() { $uuid = Str::uuid(); Str::createUuidsUsing(function () use ($uuid) { return $uuid; }); $time = Carbon::now(); Carbon::setTestNow($time); $redis = Mockery::mock(Factory::class); $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['getRandomId'])->setConstructorArgs([$redis, 'default'])->getMock(); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $container = Mockery::spy(Container::class); $queue->setContainer($container); $redis->shouldReceive('connection')->atLeast()->once()->andReturn($redis); $redis->expects('isCluster')->andReturn(false); $redis->expects('eval')->with(LuaScripts::push(), 2, 'queues:default', 'queues:default:notify', json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'custom' => 'taylor', 'bar' => 'foo', 'id' => 'foo', 'attempts' => 0, 'delay' => null])); Queue::createPayloadUsing(function ($connection, $queue, $payload) { return ['custom' => 'taylor']; }); Queue::createPayloadUsing(function ($connection, $queue, $payload) { return ['bar' => 'foo']; }); $id = $queue->push('foo', ['data']); $this->assertSame('foo', $id); $container->shouldHaveReceived('bound')->with('events')->twice(); Queue::createPayloadUsing(null); Str::createUuidsNormally(); } public function testDelayedPushProperlyPushesJobOntoRedis() { $uuid = Str::uuid(); Str::createUuidsUsing(function () use ($uuid) { return $uuid; }); $time = Carbon::now(); Carbon::setTestNow($time); $redis = Mockery::mock(Factory::class); $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['availableAt', 'getRandomId'])->setConstructorArgs([$redis, 'default'])->getMock(); $container = Mockery::spy(Container::class); $queue->setContainer($container); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $queue->expects($this->once())->method('availableAt')->with(1)->willReturn(2); $redis->shouldReceive('connection')->atLeast()->once()->andReturn($redis); $redis->expects('isCluster')->andReturn(false); $redis->expects('eval')->with( LuaScripts::later(), 1, 'queues:default:delayed', 2, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1]) ); $id = $queue->later(1, 'foo', ['data']); $this->assertSame('foo', $id); $container->shouldHaveReceived('bound')->with('events')->twice(); Str::createUuidsNormally(); } public function testDelayedPushWithDateTimeProperlyPushesJobOntoRedis() { $uuid = Str::uuid(); Str::createUuidsUsing(function () use ($uuid) { return $uuid; }); $time = $date = Carbon::now(); Carbon::setTestNow($time); $redis = Mockery::mock(Factory::class); $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['availableAt', 'getRandomId'])->setConstructorArgs([$redis, 'default'])->getMock(); $container = Mockery::spy(Container::class); $queue->setContainer($container); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $queue->expects($this->once())->method('availableAt')->with($date)->willReturn(5); $redis->shouldReceive('connection')->atLeast()->once()->andReturn($redis); $redis->expects('isCluster')->andReturn(false); $redis->expects('eval')->with( LuaScripts::later(), 1, 'queues:default:delayed', 5, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 5]) ); $queue->later($date->addSeconds(5), 'foo', ['data']); $container->shouldHaveReceived('bound')->with('events')->twice(); Str::createUuidsNormally(); } public function testBulkRespectsDelayAttributeWhenPushingOntoRedis() { $redis = Mockery::mock(Factory::class); $redis->expects('connection')->andReturn($redis); $redis->expects('pipeline')->andReturnUsing(function ($callback) { $callback(); }); $redis->expects('transaction')->andReturnUsing(function ($callback) { $callback(); }); $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['later', 'push'])->setConstructorArgs([$redis, 'default'])->getMock(); $queue->expects($this->once())->method('later')->with(15, $this->isInstanceOf(RedisJobWithDelayAttribute::class), ['data'], null); $queue->bulk([new RedisJobWithDelayAttribute], ['data']); } public function testGetQueueRemainsUnchangedForNonCluster() { $redis = Mockery::mock(Factory::class); $queue = new RedisQueue($redis, 'default'); $this->assertSame('queues:default', $queue->getQueue(null)); $this->assertSame('queues:emails', $queue->getQueue('emails')); } public function testGetQueueRemainsUnchangedForCluster() { $redis = Mockery::mock(Factory::class); $queue = new RedisQueue($redis, 'default'); // getQueue() should NOT add hash tags — it's unchanged $this->assertSame('queues:default', $queue->getQueue(null)); $this->assertSame('queues:emails', $queue->getQueue('emails')); } public function testGetRedisKeyReturnsPlainKeyForNonCluster() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, 'default'); $connection = Mockery::mock(\Illuminate\Redis\Connections\Connection::class); $connection->expects('isCluster')->andReturn(false); $redis->expects('connection')->andReturn($connection); $this->assertSame('queues:default', $queue->testGetQueueRedisKey(null)); $this->assertSame('queues:emails', $queue->testGetQueueRedisKey('emails')); } public function testGetRedisKeyWrapsWithHashTagsForPhpRedisCluster() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, 'default'); $connection = Mockery::mock(PhpRedisClusterConnection::class); $connection->expects('isCluster')->andReturn(true); $redis->expects('connection')->andReturn($connection); $this->assertSame('queues:{default}', $queue->testGetQueueRedisKey(null)); $this->assertSame('queues:{emails}', $queue->testGetQueueRedisKey('emails')); } public function testGetRedisKeyWrapsWithHashTagsForPredisCluster() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, 'default'); $connection = Mockery::mock(PredisClusterConnection::class); $connection->expects('isCluster')->andReturn(true); $redis->expects('connection')->andReturn($connection); $this->assertSame('queues:{default}', $queue->testGetQueueRedisKey(null)); $this->assertSame('queues:{emails}', $queue->testGetQueueRedisKey('emails')); } public function testGetRedisKeyDoesNotDoubleWrapExistingHashTags() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, '{default}'); $connection = Mockery::mock(PhpRedisClusterConnection::class); $connection->expects('isCluster')->andReturn(true); $redis->expects('connection')->andReturn($connection); $this->assertSame('queues:{default}', $queue->testGetQueueRedisKey(null)); $this->assertSame('queues:{custom}', $queue->testGetQueueRedisKey('{custom}')); } public function testGetRedisKeySkipsWrappingWhenQueueNameContainsBraces() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, 'default'); $connection = Mockery::mock(PhpRedisClusterConnection::class); $connection->expects('isCluster')->andReturn(true); $redis->expects('connection')->andReturn($connection); // Queue name already contains hash tags — skip wrapping $this->assertSame('queues:process-{batch}-results', $queue->testGetQueueRedisKey('process-{batch}-results')); } public function testGetRedisKeyWrapsEmptyHashTagOnCluster() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, 'default'); $connection = Mockery::mock(PhpRedisClusterConnection::class); $connection->expects('isCluster')->andReturn(true); $redis->expects('connection')->andReturn($connection); // Empty braces '{}' are not a valid hash tag — should still get wrapped $this->assertSame('queues:{my{}queue}', $queue->testGetQueueRedisKey('my{}queue')); } public function testGetRedisKeyWrapsUnmatchedOpeningBrace() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, 'default'); $connection = Mockery::mock(PhpRedisClusterConnection::class); $connection->expects('isCluster')->andReturn(true); $redis->expects('connection')->andReturn($connection); // Unmatched '{' is not a valid hash tag — should still get wrapped $this->assertSame('queues:{my{broken}', $queue->testGetQueueRedisKey('my{broken')); } public function testGetRedisKeyWrapsUnmatchedClosingBrace() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, 'default'); $connection = Mockery::mock(PhpRedisClusterConnection::class); $connection->expects('isCluster')->andReturn(true); $redis->expects('connection')->andReturn($connection); // Unmatched '}' is not a valid hash tag — should still get wrapped $this->assertSame('queues:{broken}queue}', $queue->testGetQueueRedisKey('broken}queue')); } public function testGetRedisKeyWrapsEmptyFirstHashTagFollowedByValidPair() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, 'default'); $connection = Mockery::mock(PhpRedisClusterConnection::class); $connection->expects('isCluster')->andReturn(true); $redis->expects('connection')->andReturn($connection); // Redis spec: the first '{}' is an empty hash tag, so the whole key is hashed // even though '{bar}' looks valid. Must be wrapped to ensure slot affinity. $this->assertSame('queues:{foo{}{bar}}', $queue->testGetQueueRedisKey('foo{}{bar}')); } public function testPushUsesGetRedisKeyForLuaScript() { $uuid = Str::uuid(); Str::createUuidsUsing(function () use ($uuid) { return $uuid; }); $time = Carbon::now(); Carbon::setTestNow($time); $redis = Mockery::mock(Factory::class); $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['getRandomId'])->setConstructorArgs([$redis, 'default'])->getMock(); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $container = Mockery::spy(Container::class); $queue->setContainer($container); $clusterConnection = Mockery::mock(PhpRedisClusterConnection::class)->shouldIgnoreMissing(); $clusterConnection->expects('isCluster')->andReturn(true); $redis->expects('connection')->times(2)->andReturn($clusterConnection); // command() is called by eval() — assert it receives hash-tagged keys $clusterConnection->expects('command')->with('eval', Mockery::on(function ($args) { return $args[0] === LuaScripts::push() && $args[2] === 2 && $args[1][0] === 'queues:{default}' && $args[1][1] === 'queues:{default}:notify'; }))->andReturn(null); $queue->push('foo', ['data']); Str::createUuidsNormally(); } public function testPushPassesUnchangedQueueToCreatePayload() { $uuid = Str::uuid(); Str::createUuidsUsing(function () use ($uuid) { return $uuid; }); $time = Carbon::now(); Carbon::setTestNow($time); $redis = Mockery::mock(Factory::class); $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['getRandomId'])->setConstructorArgs([$redis, 'default'])->getMock(); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $container = Mockery::spy(Container::class); $queue->setContainer($container); $clusterConnection = Mockery::mock(PhpRedisClusterConnection::class)->shouldIgnoreMissing(); $clusterConnection->expects('isCluster')->andReturn(true); $redis->expects('connection')->times(2)->andReturn($clusterConnection); $receivedQueue = null; Queue::createPayloadUsing(function ($connection, $queue) use (&$receivedQueue) { $receivedQueue = $queue; return []; }); $queue->push('foo', ['data']); // Payload hook should receive the unchanged getQueue() output (no hash tags) $this->assertSame('queues:default', $receivedQueue); Queue::createPayloadUsing(null); Str::createUuidsNormally(); } public function testSizeUsesGetRedisKeyOnCluster() { $redis = Mockery::mock(Factory::class); $queue = new RedisQueue($redis, 'default'); $clusterConnection = Mockery::mock(PhpRedisClusterConnection::class)->shouldIgnoreMissing(); $clusterConnection->expects('isCluster')->andReturn(true); $redis->expects('connection')->times(2)->andReturn($clusterConnection); $clusterConnection->expects('command')->with('eval', Mockery::on(function ($args) { return $args[0] === LuaScripts::size() && $args[2] === 3 && $args[1][0] === 'queues:{default}' && $args[1][1] === 'queues:{default}:delayed' && $args[1][2] === 'queues:{default}:reserved'; }))->andReturn(5); $this->assertSame(5, $queue->size()); } public function testClearUsesGetRedisKeyOnCluster() { $redis = Mockery::mock(Factory::class); $queue = new RedisQueue($redis, 'default'); $clusterConnection = Mockery::mock(PhpRedisClusterConnection::class)->shouldIgnoreMissing(); $clusterConnection->expects('isCluster')->andReturn(true); $redis->expects('connection')->times(2)->andReturn($clusterConnection); $clusterConnection->expects('command')->with('eval', Mockery::on(function ($args) { return $args[0] === LuaScripts::clear() && $args[2] === 4 && $args[1][0] === 'queues:{default}' && $args[1][1] === 'queues:{default}:delayed' && $args[1][2] === 'queues:{default}:reserved' && $args[1][3] === 'queues:{default}:notify'; }))->andReturn(3); $this->assertSame(3, $queue->clear('default')); } public function testIsClusterConnectionCachesResult() { $redis = Mockery::mock(Factory::class); $queue = new TestableRedisQueue($redis, 'default'); $connection = Mockery::mock(PhpRedisClusterConnection::class); $connection->expects('isCluster')->andReturn(true); $redis->expects('connection')->andReturn($connection); // Multiple calls should only trigger one connection() call $this->assertTrue($queue->testIsClusterConnection()); $this->assertTrue($queue->testIsClusterConnection()); $this->assertTrue($queue->testIsClusterConnection()); } public function testAllQueueNamesStripsClusterBraces() { $redis = Mockery::mock(Factory::class); $redis->expects('connection->keys')->andReturn(['queues:{default}', 'queues:{default}:delayed', 'queues:{emails}']); $queue = new TestableRedisQueue($redis, 'default'); $this->assertSame(['default', 'emails'], $queue->testAllQueueNames()->all()); } public function testSizeResolvesTheQueueNameFromAnEnum() { $queue = new RedisQueue($redis = Mockery::mock(Factory::class), 'default'); $redis->expects('connection')->times(2)->andReturn($redis); $redis->expects('isCluster')->andReturn(false); $redis->expects('eval')->with( LuaScripts::size(), 3, 'queues:emails', 'queues:emails:delayed', 'queues:emails:reserved' )->andReturn(5); $this->assertSame(5, $queue->size(RedisQueueName::Emails)); } public function testPendingJobsResolvesTheQueueNameFromAnEnum() { $queue = new RedisQueue($redis = Mockery::mock(Factory::class), 'default'); $redis->expects('connection')->times(2)->andReturn($redis); $redis->expects('isCluster')->andReturn(false); $redis->expects('lrange')->with('queues:emails', 0, -1)->andReturn([ json_encode(['uuid' => 'uuid', 'displayName' => 'foo']), ]); $this->assertSame('emails', $queue->pendingJobs(RedisQueueName::Emails)->first()->queue); } } enum RedisQueueName: string { case Emails = 'emails'; } class TestableRedisQueue extends RedisQueue { public function testGetQueueRedisKey($queue = null) { return $this->getQueueRedisKey($queue); } public function testIsClusterConnection() { return $this->isClusterConnection(); } public function testAllQueueNames() { return $this->allQueueNames(); } } #[Delay(15)] class RedisJobWithDelayAttribute { }