diff --git a/tests/session/RedisSessionHandlerTest.php b/tests/session/RedisSessionHandlerTest.php index 52101ee..78640fd 100644 --- a/tests/session/RedisSessionHandlerTest.php +++ b/tests/session/RedisSessionHandlerTest.php @@ -6,6 +6,7 @@ use FastD\Http\Session\Handler\RedisSessionHandler; use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redis; @@ -85,4 +86,124 @@ public function testGcIsNoopBecauseRedisExpiresKeys(): void { $this->assertSame(0, (new RedisSessionHandler($this->createMock(Redis::class)))->gc()); } + + public function testReadReturnsEmptyArrayForNonStringRedisValues(): void + { + $redis = $this->createMock(Redis::class); + $redis->expects($this->exactly(3)) + ->method('get') + ->willReturnOnConsecutiveCalls(false, null, 123); + + $handler = new RedisSessionHandler($redis); + + $this->assertSame([], $handler->read('a')); + $this->assertSame([], $handler->read('b')); + $this->assertSame([], $handler->read('c')); + } + + #[DataProvider('nonArrayJsonProvider')] + public function testReadReturnsEmptyArrayForScalarJson(string $payload): void + { + $redis = $this->createMock(Redis::class); + $redis->method('get')->willReturn($payload); + + $this->assertSame([], (new RedisSessionHandler($redis))->read('scalar')); + } + + public static function nonArrayJsonProvider(): array + { + return [ + 'null' => ['null'], + 'integer' => ['123'], + 'string' => ['"value"'], + 'boolean' => ['true'], + ]; + } + + public function testReadUsesCustomPrefix(): void + { + $redis = $this->createMock(Redis::class); + $redis->expects($this->once()) + ->method('get') + ->with('custom:abc') + ->willReturn(json_encode(['ok' => true])); + + $this->assertSame(['ok' => true], (new RedisSessionHandler($redis, 60, 'custom:'))->read('abc')); + } + + public function testWriteEncodesUnicodeWithoutEscaping(): void + { + $data = ['name' => '简']; + $json = json_encode($data, JSON_UNESCAPED_UNICODE); + + $redis = $this->createMock(Redis::class); + $redis->expects($this->once()) + ->method('setex') + ->with('fastd:session:abc', 60, $json) + ->willReturn(true); + + $this->assertTrue((new RedisSessionHandler($redis, 60))->write('abc', $data)); + } + + public function testWriteAllowsPayloadAtExactMaxDataSize(): void + { + $data = ['blob' => str_repeat('x', 32)]; + $json = json_encode($data, JSON_UNESCAPED_UNICODE); + + $redis = $this->createMock(Redis::class); + $redis->expects($this->once()) + ->method('setex') + ->with('fastd:session:abc', 60, $json) + ->willReturn(true); + + $handler = new RedisSessionHandler($redis, 60, 'fastd:session:', strlen($json)); + + $this->assertTrue($handler->write('abc', $data)); + } + + public function testWriteReturnsFalseWhenJsonEncodingFails(): void + { + $redis = $this->createMock(Redis::class); + $redis->expects($this->never())->method('setex'); + + $handler = new RedisSessionHandler($redis, 60); + + $this->assertFalse($handler->write('abc', ['invalid' => "\xB1\x31"])); + } + + public function testWriteReturnsFalseWhenSetexFails(): void + { + $redis = $this->createMock(Redis::class); + $redis->expects($this->once()) + ->method('setex') + ->willReturn(false); + + $this->assertFalse((new RedisSessionHandler($redis, 60))->write('abc', ['ok' => true])); + } + + public function testDestroyReturnsFalseWhenKeyDoesNotExist(): void + { + $redis = $this->createMock(Redis::class); + $redis->expects($this->once())->method('del')->with('fastd:session:missing')->willReturn(0); + + $this->assertFalse((new RedisSessionHandler($redis))->destroy('missing')); + } + + public function testExistsReturnsFalseWhenKeyDoesNotExist(): void + { + $redis = $this->createMock(Redis::class); + $redis->expects($this->once())->method('exists')->with('fastd:session:missing')->willReturn(0); + + $this->assertFalse((new RedisSessionHandler($redis))->exists('missing')); + } + + public function testGcAcceptsMaxLifetimeAndRemainsNoop(): void + { + $redis = $this->createMock(Redis::class); + $redis->expects($this->never())->method('get'); + $redis->expects($this->never())->method('setex'); + $redis->expects($this->never())->method('del'); + + $this->assertSame(0, (new RedisSessionHandler($redis))->gc(3600)); + } }