sessionRepo = new FakeSessionRepository(); $this->clock = new FakeClock( new DateTimeImmutable('2026-05-16 12:00:00'), ); $this->middleware = new AuthMiddleware( $this->sessionRepo, $this->clock, ); $userRepo = new FakeUserRepository(); $hasher = new FakePasswordHasher(); $this->user = $userRepo->create(new CreateUserDto( email: new EmailAddress('user@example.com'), passwordHash: $hasher->hash('password'), )); $this->sessionRepo->create(new CreateSessionDto( token: 'valid-token', user: $this->user, createdAt: new DateTimeImmutable('2026-05-16 12:00:00'), expiresAt: new DateTimeImmutable('2026-05-23 12:00:00'), )); } public function testPassesRequestToHandlerWhenCookieIsValid(): void { $request = $this->createRequestWithCookie('valid-token'); $handler = $this->createMock(RequestHandlerInterface::class); $handler->expects($this->once()) ->method('handle') ->with($this->callback(function (ServerRequestInterface $request) { $user = $request->getAttribute('user'); return $user instanceof User && $user->getId() === $this->user->getId(); })) ->willReturn(new Response(200)); $response = $this->middleware->process($request, $handler); $this->assertSame(200, $response->getStatusCode()); } public function testReturns401WhenCookieIsMissing(): void { $request = self::createRequest(); $handler = $this->createMock(RequestHandlerInterface::class); $handler->expects($this->never())->method('handle'); $response = $this->middleware->process($request, $handler); $this->assertSame(401, $response->getStatusCode()); } public function testReturns401WhenCookieIsEmpty(): void { $request = $this->createRequestWithCookie(''); $handler = $this->createMock(RequestHandlerInterface::class); $handler->expects($this->never())->method('handle'); $response = $this->middleware->process($request, $handler); $this->assertSame(401, $response->getStatusCode()); } public function testReturns401WhenTokenIsUnknown(): void { $request = $this->createRequestWithCookie('unknown-token'); $handler = $this->createMock(RequestHandlerInterface::class); $handler->expects($this->never())->method('handle'); $response = $this->middleware->process($request, $handler); $this->assertSame(401, $response->getStatusCode()); } public function testReturns401WhenSessionIsExpired(): void { $this->clock->setTime( new DateTimeImmutable('2026-05-30 12:00:00'), ); $request = $this->createRequestWithCookie('valid-token'); $handler = $this->createMock(RequestHandlerInterface::class); $handler->expects($this->never())->method('handle'); $response = $this->middleware->process($request, $handler); $this->assertSame(401, $response->getStatusCode()); } public function testDeletesExpiredSession(): void { $this->clock->setTime( new DateTimeImmutable('2026-05-30 12:00:00'), ); $request = $this->createRequestWithCookie('valid-token'); $handler = $this->createStub(RequestHandlerInterface::class); $this->middleware->process($request, $handler); $this->assertNull( $this->sessionRepo->findByToken('valid-token'), ); } private static function createRequest(): ServerRequestInterface { $factory = new ServerRequestFactory(); return $factory->createServerRequest('GET', '/'); } private function createRequestWithCookie( string $token, ): ServerRequestInterface { $request = self::createRequest(); return $request->withCookieParams([ AuthMiddleware::COOKIE_NAME => $token, ]); } }