connection = mock(ConnectionInterface::class); $this->logger = mock(Logger::class); // Create services $this->workerRegistry = new WorkerRegistry($this->connection, $this->logger); $this->distributedLock = new DatabaseDistributedLock($this->connection, $this->logger); $this->jobDistribution = new JobDistributionService( $this->workerRegistry, $this->distributedLock, $this->connection, $this->logger ); $this->healthCheck = new WorkerHealthCheckService( $this->workerRegistry, $this->connection, $this->logger ); $this->failoverRecovery = new FailoverRecoveryService( $this->workerRegistry, $this->jobDistribution, $this->healthCheck, $this->distributedLock, $this->connection, $this->logger ); // Create test workers $this->worker1 = Worker::register( hostname: 'app-server-1', processId: 1001, queues: [ QueueName::emailQueue(), QueueName::defaultQueue(), ], maxJobs: 10, capabilities: ['email', 'pdf-generation'] ); $this->worker2 = Worker::register( hostname: 'app-server-2', processId: 1002, queues: [ QueueName::defaultQueue(), QueueName::fromString('high-priority'), ], maxJobs: 5, capabilities: ['image-processing', 'pdf-generation'] ); $this->worker3 = Worker::register( hostname: 'app-server-3', processId: 1003, queues: [ QueueName::emailQueue(), ], maxJobs: 15, capabilities: ['email', 'notifications'] ); // Test job IDs $this->jobId1 = JobId::generate(); $this->jobId2 = JobId::generate(); $this->jobId3 = JobId::generate(); }); describe('Worker Registration and Discovery', function () { it('can register multiple workers across different queues', function () { // Mock successful database operations for worker registration $this->connection->shouldReceive('prepare')->andReturnSelf(); $this->connection->shouldReceive('execute')->andReturn(true); $this->logger->shouldReceive('info')->andReturn(null); $this->logger->shouldReceive('debug')->andReturn(null); // Register workers $this->workerRegistry->register($this->worker1); $this->workerRegistry->register($this->worker2); $this->workerRegistry->register($this->worker3); // Verify registration calls were made expect($this->connection)->toHaveReceived('prepare')->times(3); expect($this->connection)->toHaveReceived('execute')->times(3); }); it('can find workers for specific queues', function () { // Mock database query for finding workers by queue $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('fetch')->andReturn( $this->worker1->toArray(), $this->worker3->toArray(), false // End of results ); $this->connection->shouldReceive('prepare')->andReturn($stmt); $this->logger->shouldReceive('error')->never(); $workers = $this->workerRegistry->findWorkersForQueue(QueueName::emailQueue()); expect($workers)->toHaveCount(2); expect($workers[0]->hostname)->toBe('app-server-1'); expect($workers[1]->hostname)->toBe('app-server-3'); }); it('can find best available worker with load balancing', function () { // Create workers with different load levels $lightlyLoadedWorker = $this->worker1->updateHeartbeat( new Percentage(20), // Low CPU Byte::fromMegabytes(512), // Low memory 2 // 2 out of 10 jobs ); $heavilyLoadedWorker = $this->worker2->updateHeartbeat( new Percentage(80), // High CPU Byte::fromMegabytes(1500), // High memory 4 // 4 out of 5 jobs = 80% load ); // Mock database to return workers with different loads $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('fetch')->andReturn( $lightlyLoadedWorker->toArray(), $heavilyLoadedWorker->toArray(), false ); $this->connection->shouldReceive('prepare')->andReturn($stmt); $this->logger->shouldReceive('error')->never(); $bestWorker = $this->workerRegistry->findBestWorkerForQueue(QueueName::defaultQueue()); expect($bestWorker)->not->toBeNull(); expect($bestWorker->hostname)->toBe('app-server-1'); // Should pick lightly loaded worker }); it('correctly calculates worker load percentages', function () { $worker = $this->worker1->updateHeartbeat( new Percentage(30), // 30% CPU Byte::fromMegabytes(800), 3 // 3 out of 10 jobs = 30% job load ); $loadPercentage = $worker->getLoadPercentage(); // Should take the higher of CPU load (30%) or job load (30%) expect($loadPercentage->getValue())->toBe(30.0); // Test with higher CPU load $workerHighCpu = $worker->updateHeartbeat( new Percentage(75), // 75% CPU Byte::fromMegabytes(800), 3 // Still 30% job load ); expect($workerHighCpu->getLoadPercentage()->getValue())->toBe(75.0); }); }); describe('Distributed Locking System', function () { it('prevents race conditions when acquiring job locks', function () { $lockKey = LockKey::forJob($this->jobId1); $workerId1 = WorkerId::generate(); $workerId2 = WorkerId::generate(); $ttl = Duration::fromMinutes(5); // Mock first worker successfully acquiring lock $stmt1 = mock(\PDOStatement::class); $stmt1->shouldReceive('execute')->andReturn(true); $stmt1->shouldReceive('rowCount')->andReturn(1); // Successful insert // Mock second worker failing to acquire same lock (duplicate key) $stmt2 = mock(\PDOStatement::class); $stmt2->shouldReceive('execute')->andThrow( new \PDOException('Duplicate entry for key PRIMARY') ); $this->connection->shouldReceive('prepare') ->andReturn($stmt1, $stmt2); $this->logger->shouldReceive('debug')->andReturn(null); $this->logger->shouldReceive('info')->andReturn(null); // First worker should successfully acquire lock $result1 = $this->distributedLock->acquire($lockKey, $workerId1, $ttl); expect($result1)->toBeTrue(); // Second worker should fail to acquire same lock $result2 = $this->distributedLock->acquire($lockKey, $workerId2, $ttl); expect($result2)->toBeFalse(); }); it('can extend lock duration for active workers', function () { $lockKey = LockKey::forJob($this->jobId1); $workerId = WorkerId::generate(); $extension = Duration::fromMinutes(10); // Mock successful lock extension $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('rowCount')->andReturn(1); // Lock was extended $this->connection->shouldReceive('prepare')->andReturn($stmt); $this->logger->shouldReceive('debug')->andReturn(null); $result = $this->distributedLock->extend($lockKey, $workerId, $extension); expect($result)->toBeTrue(); }); it('can release locks and clean up resources', function () { $lockKey = LockKey::forJob($this->jobId1); $workerId = WorkerId::generate(); // Mock successful lock release $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('rowCount')->andReturn(1); // Lock was deleted $this->connection->shouldReceive('prepare')->andReturn($stmt); $this->logger->shouldReceive('debug')->andReturn(null); $this->logger->shouldReceive('info')->andReturn(null); $result = $this->distributedLock->release($lockKey, $workerId); expect($result)->toBeTrue(); }); it('supports lock acquisition with timeout for competing workers', function () { $lockKey = LockKey::forQueue(QueueName::defaultQueue()); $workerId = WorkerId::generate(); $ttl = Duration::fromMinutes(5); $timeout = Duration::fromSeconds(2); // Mock first attempt fails, second attempt succeeds $stmt1 = mock(\PDOStatement::class); $stmt1->shouldReceive('execute')->andThrow( new \PDOException('Duplicate entry') ); $stmt2 = mock(\PDOStatement::class); $stmt2->shouldReceive('execute')->andReturn(true); $stmt2->shouldReceive('rowCount')->andReturn(1); $this->connection->shouldReceive('prepare') ->andReturn($stmt1, $stmt2); $this->logger->shouldReceive('debug')->andReturn(null); $this->logger->shouldReceive('info')->andReturn(null); $result = $this->distributedLock->acquireWithTimeout($lockKey, $workerId, $ttl, $timeout); expect($result)->toBeTrue(); }); }); describe('Job Distribution Service', function () { it('can distribute jobs to best available workers', function () { // Mock distribution lock acquisition $distributionStmt = mock(\PDOStatement::class); $distributionStmt->shouldReceive('execute')->andReturn(true); $distributionStmt->shouldReceive('rowCount')->andReturn(1); // Mock job lock acquisition $jobStmt = mock(\PDOStatement::class); $jobStmt->shouldReceive('execute')->andReturn(true); $jobStmt->shouldReceive('rowCount')->andReturn(1); // Mock job assignment recording $assignmentStmt = mock(\PDOStatement::class); $assignmentStmt->shouldReceive('execute')->andReturn(true); // Mock finding workers for queue $workerStmt = mock(\PDOStatement::class); $workerStmt->shouldReceive('execute')->andReturn(true); $workerStmt->shouldReceive('fetch')->andReturn( $this->worker1->toArray(), false ); $this->connection->shouldReceive('prepare')->andReturn( $distributionStmt, // Distribution lock $jobStmt, // Job lock $jobStmt, // Job lock transfer (release old) $jobStmt, // Job lock transfer (acquire new) $assignmentStmt, // Job assignment $jobStmt, // Release distribution lock $workerStmt // Find workers ); $this->logger->shouldReceive('info')->andReturn(null); $this->logger->shouldReceive('debug')->andReturn(null); $this->logger->shouldReceive('warning')->never(); $this->logger->shouldReceive('error')->never(); $assignedWorkerId = $this->jobDistribution->distributeJob( $this->jobId1, QueueName::defaultQueue(), ['priority' => 'normal'] ); expect($assignedWorkerId)->not->toBeNull(); expect($assignedWorkerId->toString())->toBe($this->worker1->id->toString()); }); it('calculates worker scores based on load and capabilities', function () { $jobData = [ 'required_capabilities' => ['email', 'pdf-generation'], ]; $bestWorker = $this->jobDistribution->findBestWorkerForJob( QueueName::emailQueue(), $jobData ); // Should return null when no workers are mocked in database // In real scenario, would return worker with matching capabilities and lowest load expect($bestWorker)->toBeNull(); }); it('handles job distribution when no workers are available', function () { // Mock empty worker result set $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('fetch')->andReturn(false); // No workers found $this->connection->shouldReceive('prepare')->andReturn($stmt); $this->logger->shouldReceive('warning')->andReturn(null); $this->logger->shouldReceive('error')->never(); $result = $this->jobDistribution->findBestWorkerForJob(QueueName::defaultQueue()); expect($result)->toBeNull(); }); it('can release jobs from workers and cleanup assignments', function () { // Mock successful job release $lockStmt = mock(\PDOStatement::class); $lockStmt->shouldReceive('execute')->andReturn(true); $lockStmt->shouldReceive('rowCount')->andReturn(1); // Mock assignment cleanup $assignmentStmt = mock(\PDOStatement::class); $assignmentStmt->shouldReceive('execute')->andReturn(true); $this->connection->shouldReceive('prepare')->andReturn( $lockStmt, // Release job lock $assignmentStmt // Delete assignment ); $this->logger->shouldReceive('info')->andReturn(null); $this->logger->shouldReceive('debug')->andReturn(null); $result = $this->jobDistribution->releaseJob($this->jobId1, $this->worker1->id); expect($result)->toBeTrue(); }); }); describe('Worker Health Monitoring', function () { it('detects unhealthy workers based on resource usage', function () { // Create worker with critical resource usage $unhealthyWorker = $this->worker1->updateHeartbeat( new Percentage(95), // Critical CPU usage Byte::fromGigabytes(2.5), // Exceeds memory limit 8 // High job count but within limits ); expect($unhealthyWorker->isHealthy())->toBeFalse(); expect($unhealthyWorker->isAvailableForJobs())->toBeFalse(); }); it('detects workers with stale heartbeats', function () { // Create worker with old heartbeat $staleWorker = new Worker( id: $this->worker1->id, hostname: $this->worker1->hostname, processId: $this->worker1->processId, queues: $this->worker1->queues, maxJobs: $this->worker1->maxJobs, registeredAt: $this->worker1->registeredAt, lastHeartbeat: new \DateTimeImmutable('-5 minutes'), // Stale heartbeat isActive: true, cpuUsage: new Percentage(30), memoryUsage: Byte::fromMegabytes(512), currentJobs: 2 ); expect($staleWorker->isHealthy())->toBeFalse(); }); it('identifies healthy workers correctly', function () { $healthyWorker = $this->worker1->updateHeartbeat( new Percentage(45), // Normal CPU usage Byte::fromMegabytes(800), // Normal memory usage 3 // Normal job count ); expect($healthyWorker->isHealthy())->toBeTrue(); expect($healthyWorker->isAvailableForJobs())->toBeTrue(); }); it('considers workers at capacity as unavailable but healthy', function () { $atCapacityWorker = $this->worker2->updateHeartbeat( new Percentage(50), // Normal CPU Byte::fromMegabytes(600), // Normal memory 5 // At max capacity (5/5 jobs) ); expect($atCapacityWorker->isHealthy())->toBeTrue(); expect($atCapacityWorker->isAvailableForJobs())->toBeFalse(); // At capacity }); }); describe('Multi-Worker Load Distribution', function () { it('distributes jobs across multiple workers evenly', function () { $workers = [$this->worker1, $this->worker2, $this->worker3]; $jobs = [$this->jobId1, $this->jobId2, $this->jobId3]; // Mock successful distribution for all jobs foreach ($jobs as $index => $jobId) { // Each job gets distributed to a different worker $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('rowCount')->andReturn(1); $stmt->shouldReceive('fetch')->andReturn( $workers[$index]->toArray(), false ); $this->connection->shouldReceive('prepare')->andReturn($stmt); } $this->logger->shouldReceive('info')->andReturn(null); $this->logger->shouldReceive('debug')->andReturn(null); $assignments = []; foreach ($jobs as $jobId) { $workerId = $this->jobDistribution->distributeJob( $jobId, QueueName::defaultQueue() ); if ($workerId) { $assignments[] = $workerId->toString(); } } // Should have distributed jobs (exact distribution depends on mocking) expect($assignments)->not->toBeEmpty(); }); it('handles worker overload by selecting alternative workers', function () { // Create overloaded worker $overloadedWorker = $this->worker1->updateHeartbeat( new Percentage(95), // Critical CPU Byte::fromGigabytes(2.5), // Over memory limit 10 // At max capacity ); // Create available alternative worker $availableWorker = $this->worker2->updateHeartbeat( new Percentage(20), // Low CPU Byte::fromMegabytes(400), // Low memory 1 // Low job count ); // Verify overloaded worker is not available expect($overloadedWorker->isAvailableForJobs())->toBeFalse(); // Verify alternative worker is available expect($availableWorker->isAvailableForJobs())->toBeTrue(); }); }); describe('Automatic Failover and Recovery', function () { it('detects failed workers and initiates recovery', function () { // Create failed worker (old heartbeat, high resource usage) $failedWorker = new Worker( id: $this->worker1->id, hostname: $this->worker1->hostname, processId: $this->worker1->processId, queues: $this->worker1->queues, maxJobs: $this->worker1->maxJobs, registeredAt: $this->worker1->registeredAt, lastHeartbeat: new \DateTimeImmutable('-10 minutes'), // Very stale isActive: true, cpuUsage: new Percentage(99), // Critical CPU memoryUsage: Byte::fromGigabytes(3), // Over limit currentJobs: 5 ); expect($failedWorker->isHealthy())->toBeFalse(); expect($failedWorker->isAvailableForJobs())->toBeFalse(); }); it('can reassign jobs from failed workers to healthy workers', function () { $failedWorkerId = WorkerId::generate(); $healthyWorkerId = WorkerId::generate(); // Mock job reassignment operations $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('rowCount')->andReturn(2); // 2 jobs reassigned $this->connection->shouldReceive('prepare')->andReturn($stmt); $this->logger->shouldReceive('info')->andReturn(null); $releasedJobs = $this->jobDistribution->releaseAllWorkerJobs($failedWorkerId); expect($releasedJobs)->toBeGreaterThanOrEqual(0); }); it('cleans up resources from inactive workers', function () { // Mock worker cleanup operations $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('rowCount')->andReturn(3); // 3 workers deactivated $this->connection->shouldReceive('prepare')->andReturn($stmt); $this->logger->shouldReceive('info')->andReturn(null); $cleanedCount = $this->workerRegistry->cleanupInactiveWorkers(5); expect($cleanedCount)->toBe(3); }); }); describe('System Resilience and Stress Testing', function () { it('handles concurrent job distribution requests', function () { $concurrentJobs = [ JobId::generate(), JobId::generate(), JobId::generate(), JobId::generate(), JobId::generate(), ]; // Mock successful distribution for all concurrent jobs foreach ($concurrentJobs as $jobId) { $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('rowCount')->andReturn(1); $stmt->shouldReceive('fetch')->andReturn( $this->worker1->toArray(), false ); $this->connection->shouldReceive('prepare')->andReturn($stmt); } $this->logger->shouldReceive('info')->andReturn(null); $this->logger->shouldReceive('debug')->andReturn(null); $successfulDistributions = 0; foreach ($concurrentJobs as $jobId) { $workerId = $this->jobDistribution->distributeJob( $jobId, QueueName::defaultQueue() ); if ($workerId) { $successfulDistributions++; } } expect($successfulDistributions)->toBeGreaterThanOrEqual(0); }); it('maintains system consistency during lock contention', function () { $lockKey = LockKey::forQueue(QueueName::defaultQueue()); $workers = [ WorkerId::generate(), WorkerId::generate(), WorkerId::generate(), ]; // Simulate lock contention - only first worker succeeds $successStmt = mock(\PDOStatement::class); $successStmt->shouldReceive('execute')->andReturn(true); $successStmt->shouldReceive('rowCount')->andReturn(1); $failStmt = mock(\PDOStatement::class); $failStmt->shouldReceive('execute')->andThrow( new \PDOException('Duplicate entry') ); $this->connection->shouldReceive('prepare')->andReturn( $successStmt, // First worker succeeds $failStmt, // Second worker fails $failStmt // Third worker fails ); $this->logger->shouldReceive('debug')->andReturn(null); $this->logger->shouldReceive('info')->andReturn(null); $results = []; foreach ($workers as $workerId) { $result = $this->distributedLock->acquire( $lockKey, $workerId, Duration::fromMinutes(5) ); $results[] = $result; } // Only one worker should succeed $successCount = array_sum($results); expect($successCount)->toBe(1); }); it('recovers gracefully from database connection failures', function () { // Mock database connection failure $this->connection->shouldReceive('prepare')->andThrow( new \PDOException('Connection lost') ); $this->logger->shouldReceive('error')->andReturn(null); // System should handle gracefully and throw appropriate exception expect(fn () => $this->workerRegistry->findActiveWorkers()) ->toThrow(\PDOException::class); }); it('provides comprehensive system statistics for monitoring', function () { // Mock statistics queries $statsStmt = mock(\PDOStatement::class); $statsStmt->shouldReceive('execute')->andReturn(true); $statsStmt->shouldReceive('fetch')->andReturn([ 'total_workers' => 3, 'active_workers' => 2, 'healthy_workers' => 2, 'unique_hosts' => 2, 'total_capacity' => 30, 'current_load' => 10, 'avg_cpu_usage' => 45.5, 'avg_memory_usage' => 819200000, // ~800MB in bytes ]); $queueStmt = mock(\PDOStatement::class); $queueStmt->shouldReceive('execute')->andReturn(true); $queueStmt->shouldReceive('fetch')->andReturn( ['queues' => '["default", "email"]'], ['queues' => '["default", "high-priority"]'], false ); $this->connection->shouldReceive('prepare')->andReturn( $statsStmt, $queueStmt ); $this->logger->shouldReceive('error')->never(); $statistics = $this->workerRegistry->getWorkerStatistics(); expect($statistics)->toHaveKey('total_workers'); expect($statistics)->toHaveKey('active_workers'); expect($statistics)->toHaveKey('capacity_utilization'); expect($statistics['total_workers'])->toBe(3); expect($statistics['active_workers'])->toBe(2); }); }); describe('Value Object Behavior', function () { it('ensures WorkerId uniqueness and proper formatting', function () { $workerId1 = WorkerId::generate(); $workerId2 = WorkerId::generate(); expect($workerId1->toString())->not->toBe($workerId2->toString()); expect($workerId1->equals($workerId2))->toBeFalse(); // Test host-based WorkerId $hostWorkerId = WorkerId::forHost('test-host', 1234); expect($hostWorkerId->toString())->toContain('test-host'); }); it('validates LockKey patterns and constraints', function () { $jobLock = LockKey::forJob($this->jobId1); $queueLock = LockKey::forQueue(QueueName::defaultQueue()); $workerLock = LockKey::forWorker($this->worker1->id); expect($jobLock->toString())->toStartWith('job.'); expect($queueLock->toString())->toStartWith('queue.'); expect($workerLock->toString())->toStartWith('worker.'); // Test lock key modifications $prefixedLock = $jobLock->withPrefix('tenant-1'); $suffixedLock = $queueLock->withSuffix('processing'); expect($prefixedLock->toString())->toStartWith('tenant-1.job.'); expect($suffixedLock->toString())->toEndWith('.processing'); }); it('validates JobId generation and uniqueness', function () { $jobId1 = JobId::generate(); $jobId2 = JobId::generate(); expect($jobId1->toString())->not->toBe($jobId2->toString()); expect($jobId1->equals($jobId2))->toBeFalse(); // Test string conversion $jobIdFromString = JobId::fromString($jobId1->toString()); expect($jobIdFromString->equals($jobId1))->toBeTrue(); }); it('properly handles QueueName creation and equality', function () { $queue1 = QueueName::defaultQueue(); $queue2 = QueueName::emailQueue(); $queue3 = QueueName::fromString('custom-queue'); expect($queue1->toString())->toBe('default'); expect($queue2->toString())->toBe('email'); expect($queue3->toString())->toBe('custom-queue'); expect($queue1->equals($queue2))->toBeFalse(); expect($queue1->equals(QueueName::defaultQueue()))->toBeTrue(); }); }); describe('Edge Cases and Error Scenarios', function () { it('handles worker registration with invalid data gracefully', function () { expect(fn () => Worker::register( hostname: '', // Invalid empty hostname processId: 1001, queues: [QueueName::defaultQueue()], maxJobs: 10 ))->toThrow(\InvalidArgumentException::class); expect(fn () => Worker::register( hostname: 'valid-host', processId: 1001, queues: [], // Invalid empty queues maxJobs: 10 ))->toThrow(\InvalidArgumentException::class); expect(fn () => Worker::register( hostname: 'valid-host', processId: 1001, queues: [QueueName::defaultQueue()], maxJobs: 0 // Invalid max jobs ))->toThrow(\InvalidArgumentException::class); }); it('handles lock key validation properly', function () { expect(fn () => LockKey::fromString('')) ->toThrow(\InvalidArgumentException::class); expect(fn () => LockKey::fromString(str_repeat('a', 256))) // Too long ->toThrow(\InvalidArgumentException::class); expect(fn () => LockKey::fromString('invalid@key!')) // Invalid characters ->toThrow(\InvalidArgumentException::class); }); it('handles job distribution when all workers are at capacity', function () { // Mock database returning workers at full capacity $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andReturn(true); $stmt->shouldReceive('fetch')->andReturn(false); // No available workers $this->connection->shouldReceive('prepare')->andReturn($stmt); $this->logger->shouldReceive('warning')->andReturn(null); $result = $this->jobDistribution->findBestWorkerForJob(QueueName::defaultQueue()); expect($result)->toBeNull(); }); it('handles lock acquisition timeout correctly', function () { $lockKey = LockKey::forJob($this->jobId1); $workerId = WorkerId::generate(); $ttl = Duration::fromMinutes(5); $shortTimeout = Duration::fromMilliseconds(100); // Very short timeout // Mock all acquisition attempts fail $stmt = mock(\PDOStatement::class); $stmt->shouldReceive('execute')->andThrow( new \PDOException('Duplicate entry') ); $this->connection->shouldReceive('prepare')->andReturn($stmt); $this->logger->shouldReceive('debug')->andReturn(null); $this->logger->shouldReceive('info')->andReturn(null); $result = $this->distributedLock->acquireWithTimeout( $lockKey, $workerId, $ttl, $shortTimeout ); expect($result)->toBeFalse(); }); }); });