diff --git a/.github/workflows/testo.yml b/.github/workflows/testo.yml index 174bb5a..811fc83 100644 --- a/.github/workflows/testo.yml +++ b/.github/workflows/testo.yml @@ -14,3 +14,4 @@ jobs: ['ubuntu-latest'] dependencies: >- ['lowest', 'highest'] + ini-values: memory_limit=-1, zend.assertions=1 diff --git a/tests/src/LockTest.php b/tests/src/LockTest.php index 3ae872d..b95a21f 100644 --- a/tests/src/LockTest.php +++ b/tests/src/LockTest.php @@ -6,6 +6,7 @@ use Mockery as m; use Mockery\MockInterface; +use Ramsey\Uuid\Uuid; use RoadRunner\Lock\DTO\V1BETA1\Request; use RoadRunner\Lock\DTO\V1BETA1\Response; use RoadRunner\Lock\Lock; @@ -14,8 +15,11 @@ use Spiral\Goridge\RPC\RPCInterface; use Testo\Assert; use Testo\Assert\ExpectException; +use Testo\Core\Exception\SkipTest; use Testo\Data\DataProvider; +use Testo\Expect; use Testo\Lifecycle\BeforeTest; +use Testo\Skip; use Testo\Test; #[Test] @@ -79,6 +83,26 @@ public static function resultDataProvider(): \Traversable yield [false]; } + public static function negativeTimeDataProvider(): \Generator + { + yield 'lock ttl' => ['lock', ['resource', 'uuid', -300]]; + yield 'lock waitTTL' => ['lock', ['resource', 'uuid', 0, -300]]; + yield 'lockRead ttl' => ['lockRead', ['resource', 'uuid', -300]]; + yield 'lockRead waitTTL' => ['lockRead', ['resource', 'uuid', 0, -300]]; + yield 'updateTTL ttl' => ['updateTTL', ['resource', 'uuid', -300]]; + } + + public static function compoundIntervalDataProvider(): \Generator + { + yield 'minutes and seconds' => [new \DateInterval('PT1M30S'), 90_000_000]; + yield 'hours' => [new \DateInterval('PT1H'), 3_600_000_000]; + yield 'days' => [new \DateInterval('P1D'), 86_400_000_000]; + + $interval = new \DateInterval('PT1S'); + $interval->f = 0.5; + yield 'fraction of a second' => [$interval, 1_500_000]; + } + #[DataProvider('lockTypeDataProvider')] public function testLock( string $method, @@ -199,34 +223,50 @@ public function testExistsWithId(bool $result): void Assert::same($this->lock->exists('resource', 'some-id'), $result); } - #[ExpectException(\LogicException::class)] - public function testLockNegativeTtl(): void + #[DataProvider('negativeTimeDataProvider')] + public function testNegativeTimeFailsAssertion(string $method, array $args): void { - $this->lock->lock('resource', 'uuid', -300); - } + if (\ini_get('zend.assertions') !== '1') { + throw new SkipTest('The negative time check is an assert(), inactive unless zend.assertions=1'); + } - #[ExpectException(\LogicException::class)] - public function testLockNegativeWaitTtl(): void - { - $this->lock->lock('resource', 'uuid', 0, -300); + Expect::exception(\AssertionError::class); + $this->lock->$method(...$args); } - #[ExpectException(\LogicException::class)] - public function testLockReadNegativeTtl(): void + #[Skip('Bug: the docblock promises InvalidArgumentException for a negative ttl, but the check is an assert(); with assertions off the negative value is sent to RoadRunner')] + #[DataProvider('negativeTimeDataProvider')] + #[ExpectException(\InvalidArgumentException::class)] + public function testNegativeTimeThrowsInvalidArgumentException(string $method, array $args): void { - $this->lock->lock('resource', 'uuid', -300); + $this->lock->$method(...$args); } - #[ExpectException(\LogicException::class)] - public function testLockReadNegativeWaitTtl(): void + #[Skip('Bug: convertTimeToMicroseconds() reads only the seconds field of a DateInterval (format("%s")), dropping minutes, hours, days and microseconds')] + #[DataProvider('compoundIntervalDataProvider')] + public function testCompoundDateIntervalTtl(\DateInterval $ttl, int $expectedTtl): void { - $this->lock->lockRead('resource', 'uuid', 0, -300); + $this->rpc->shouldReceive('call') + ->once() + ->withArgs(static fn(string $method, Request $request): bool => $request->getTtl() === $expectedTtl) + ->andReturn(new Response(['ok' => true])); + + Assert::true($this->lock->updateTTL('resource', 'some-id', $ttl)); } - #[ExpectException(\LogicException::class)] - public function testUpdateNegativeWaitTtl(): void + public function testLockGeneratesUuidByDefault(): void { - $this->lock->updateTTL('resource', 'uuid', -300); + $rpc = m::mock(RPCInterface::class); + $rpc->shouldReceive('withCodec')->andReturnSelf(); + $rpc->shouldReceive('call') + ->once() + ->withArgs(static fn(string $method, Request $request): bool => Uuid::isValid($request->getId())) + ->andReturn(new Response(['ok' => true])); + + $id = (new Lock($rpc))->lock('resource'); + + Assert::string($id); + Assert::same(Uuid::fromString($id)->getFields()->getVersion(), 4); } #[BeforeTest] diff --git a/tests/src/UuidLockIdGeneratorTest.php b/tests/src/UuidLockIdGeneratorTest.php new file mode 100644 index 0000000..39ae691 --- /dev/null +++ b/tests/src/UuidLockIdGeneratorTest.php @@ -0,0 +1,36 @@ +generate(); + + Assert::true(Uuid::isValid($id)); + Assert::same(Uuid::fromString($id)->getFields()->getVersion(), 4); + Assert::notSame($generator->generate(), $id); + } + + public function testUsesGivenFactory(): void + { + $uuid = Uuid::fromString('2d1e4f7a-8b3c-4d5e-9f60-718293a4b5c6'); + $factory = m::mock(UuidFactoryInterface::class); + $factory->shouldReceive('uuid4')->once()->andReturn($uuid); + + Assert::same((new UuidLockIdGenerator($factory))->generate(), '2d1e4f7a-8b3c-4d5e-9f60-718293a4b5c6'); + } +}