cache = new ArrayAdapter(); $this->repository = new RedisSessionRepository($this->cache); } #[Test] public function savePersistsSession(): void { $session = $this->createSession(); $this->repository->save($session, 86400); $retrieved = $this->repository->findByFamilyId($session->familyId); self::assertNotNull($retrieved); self::assertTrue($session->familyId->equals($retrieved->familyId)); } #[Test] public function findByFamilyIdReturnsNullWhenNotFound(): void { $nonExistentId = TokenFamilyId::generate(); $result = $this->repository->findByFamilyId($nonExistentId); self::assertNull($result); } #[Test] public function findAllByUserIdReturnsAllUserSessions(): void { $userId = UserId::generate(); $tenantId = TenantId::fromString(self::TENANT_ID); $session1 = Session::create( TokenFamilyId::generate(), $userId, $tenantId, DeviceInfo::fromUserAgent('Chrome'), Location::unknown(), new DateTimeImmutable(), ); $session2 = Session::create( TokenFamilyId::generate(), $userId, $tenantId, DeviceInfo::fromUserAgent('Firefox'), Location::unknown(), new DateTimeImmutable(), ); $otherUserSession = Session::create( TokenFamilyId::generate(), UserId::generate(), $tenantId, DeviceInfo::fromUserAgent('Safari'), Location::unknown(), new DateTimeImmutable(), ); $this->repository->save($session1, 86400); $this->repository->save($session2, 86400); $this->repository->save($otherUserSession, 86400); $sessions = $this->repository->findAllByUserId($userId); self::assertCount(2, $sessions); $familyIds = array_map( static fn (Session $s) => (string) $s->familyId, $sessions, ); self::assertContains((string) $session1->familyId, $familyIds); self::assertContains((string) $session2->familyId, $familyIds); } #[Test] public function deleteRemovesSession(): void { $session = $this->createSession(); $this->repository->save($session, 86400); $this->repository->delete($session->familyId); self::assertNull($this->repository->findByFamilyId($session->familyId)); } #[Test] public function deleteRemovesSessionFromUserIndex(): void { $userId = UserId::generate(); $session = Session::create( TokenFamilyId::generate(), $userId, TenantId::fromString(self::TENANT_ID), DeviceInfo::fromUserAgent('Chrome'), Location::unknown(), new DateTimeImmutable(), ); $this->repository->save($session, 86400); $this->repository->delete($session->familyId); $sessions = $this->repository->findAllByUserId($userId); self::assertCount(0, $sessions); } #[Test] public function deleteAllExceptRemovesOtherSessions(): void { $userId = UserId::generate(); $tenantId = TenantId::fromString(self::TENANT_ID); $currentFamilyId = TokenFamilyId::generate(); $currentSession = Session::create( $currentFamilyId, $userId, $tenantId, DeviceInfo::fromUserAgent('Chrome'), Location::unknown(), new DateTimeImmutable(), ); $otherSession1 = Session::create( TokenFamilyId::generate(), $userId, $tenantId, DeviceInfo::fromUserAgent('Firefox'), Location::unknown(), new DateTimeImmutable(), ); $otherSession2 = Session::create( TokenFamilyId::generate(), $userId, $tenantId, DeviceInfo::fromUserAgent('Safari'), Location::unknown(), new DateTimeImmutable(), ); $this->repository->save($currentSession, 86400); $this->repository->save($otherSession1, 86400); $this->repository->save($otherSession2, 86400); $deletedFamilyIds = $this->repository->deleteAllExcept($userId, $currentFamilyId); self::assertCount(2, $deletedFamilyIds); self::assertContains((string) $otherSession1->familyId, array_map(static fn ($id) => (string) $id, $deletedFamilyIds)); self::assertContains((string) $otherSession2->familyId, array_map(static fn ($id) => (string) $id, $deletedFamilyIds)); $sessions = $this->repository->findAllByUserId($userId); self::assertCount(1, $sessions); self::assertTrue($sessions[0]->familyId->equals($currentFamilyId)); } #[Test] public function updateActivityUpdatesLastActivityTimestamp(): void { $session = $this->createSession(); $this->repository->save($session, 86400); $newActivityAt = new DateTimeImmutable('2026-02-01 15:30:00'); $remainingTtl = 43200; // 12 hours remaining $this->repository->updateActivity($session->familyId, $newActivityAt, $remainingTtl); $updated = $this->repository->findByFamilyId($session->familyId); self::assertNotNull($updated); self::assertEquals($newActivityAt, $updated->lastActivityAt); } #[Test] public function savePreservesAllSessionData(): void { $familyId = TokenFamilyId::generate(); $userId = UserId::generate(); $tenantId = TenantId::fromString(self::TENANT_ID); $deviceInfo = DeviceInfo::fromUserAgent( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0', ); $location = Location::fromIp('8.8.8.8', 'United States', 'Mountain View'); $createdAt = new DateTimeImmutable('2026-01-31 10:00:00'); $session = Session::create( $familyId, $userId, $tenantId, $deviceInfo, $location, $createdAt, ); $this->repository->save($session, 86400); $retrieved = $this->repository->findByFamilyId($familyId); self::assertNotNull($retrieved); self::assertTrue($familyId->equals($retrieved->familyId)); self::assertTrue($userId->equals($retrieved->userId)); self::assertTrue($tenantId->equals($retrieved->tenantId)); self::assertSame('Desktop', $retrieved->deviceInfo->device); self::assertSame('Chrome 120', $retrieved->deviceInfo->browser); self::assertSame('Windows 10', $retrieved->deviceInfo->os); self::assertSame('United States', $retrieved->location->country); self::assertSame('Mountain View', $retrieved->location->city); self::assertEquals($createdAt, $retrieved->createdAt); } private function createSession(): Session { return Session::create( TokenFamilyId::generate(), UserId::generate(), TenantId::fromString(self::TENANT_ID), DeviceInfo::fromUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0'), Location::fromIp('192.168.1.1', 'France', 'Paris'), new DateTimeImmutable('2026-01-31 10:00:00'), ); } }