| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Integration\Jobs;
- use App\Infrastructure\Jobs\JobLockRepository;
- use App\Tests\Integration\Support\AppTestCase;
- use DateTimeImmutable;
- use DateTimeZone;
- /**
- * Lock semantics tested directly against SQLite. Two scenarios that matter
- * for SPEC §4 (`tryAcquire`):
- * 1. Two concurrent acquires → one wins, one returns false.
- * 2. An expired lock from a crashed prior owner is reclaimed on next
- * acquire (the delete-if-expired pre-step inside the transaction).
- */
- final class JobLockRepositoryTest extends AppTestCase
- {
- private JobLockRepository $locks;
- protected function setUp(): void
- {
- parent::setUp();
- $this->locks = new JobLockRepository($this->db);
- }
- public function testAcquireReleaseAcquireRoundTrip(): void
- {
- $now = new DateTimeImmutable('2026-04-29T00:00:00Z', new DateTimeZone('UTC'));
- $expires = $now->modify('+5 minutes');
- self::assertTrue($this->locks->tryAcquire('demo', $now, $expires, 'A'));
- $this->locks->release('demo', 'A');
- self::assertTrue($this->locks->tryAcquire('demo', $now, $expires, 'B'));
- }
- public function testSecondAcquireWhileHeldReturnsFalse(): void
- {
- $now = new DateTimeImmutable('2026-04-29T00:00:00Z', new DateTimeZone('UTC'));
- $expires = $now->modify('+5 minutes');
- self::assertTrue($this->locks->tryAcquire('demo', $now, $expires, 'A'));
- self::assertFalse($this->locks->tryAcquire('demo', $now, $expires, 'B'));
- }
- public function testExpiredLockReclaimedOnNextAcquire(): void
- {
- $past = new DateTimeImmutable('2026-04-29T00:00:00Z', new DateTimeZone('UTC'));
- $expires = $past->modify('+5 minutes');
- self::assertTrue($this->locks->tryAcquire('demo', $past, $expires, 'A'));
- // jump forward past expires_at — the next acquire must succeed
- // because the in-transaction delete sweeps the stale row first.
- $now = $past->modify('+1 hour');
- self::assertTrue($this->locks->tryAcquire('demo', $now, $now->modify('+5 minutes'), 'B'));
- // The lock is now held by B.
- $status = $this->locks->status('demo');
- self::assertNotNull($status);
- self::assertSame('B', $status['acquired_by']);
- }
- public function testReleaseWithDifferentOwnerIsNoOp(): void
- {
- $now = new DateTimeImmutable('2026-04-29T00:00:00Z', new DateTimeZone('UTC'));
- self::assertTrue($this->locks->tryAcquire('demo', $now, $now->modify('+5 minutes'), 'A'));
- // Defensive: B can't kick A out by releasing.
- $this->locks->release('demo', 'B');
- self::assertFalse($this->locks->tryAcquire('demo', $now, $now->modify('+5 minutes'), 'C'));
- }
- }
|