userRepository = new FakeUserRepository; $this->passwordHasher = new FakePasswordHasher; $this->sessionRepository = new FakeSessionRepository; $authenticateUser = new AuthenticateUser( $this->userRepository, $this->passwordHasher, ); $createSession = new CreateSession( $this->sessionRepository, new FakeTokenGenerator(['session-token']), new FakeClock(new DateTimeImmutable( '2026-07-31T12:00:00', new DateTimeZone('UTC'), )), ); $logout = new Logout($this->sessionRepository); $this->controller = new AuthController( $authenticateUser, $createSession, $logout, ); } public function test_login_returns_user_and_cookie(): void { $this->createUser('correct-password'); $response = $this->controller->login(new Request([ 'email' => 'user@example.com', 'password' => 'correct-password', ])); $this->assertSame(200, $response->getStatusCode()); $this->assertSame( 'user@example.com', json_decode($response->getContent(), true)['user']['email'], ); $cookie = $response->headers->getCookies()[0]; $this->assertSame(AuthMiddleware::COOKIE_NAME, $cookie->getName()); $this->assertSame('session-token', $cookie->getValue()); $this->assertTrue($cookie->isHttpOnly()); $this->assertSame('lax', $cookie->getSameSite()); $this->assertNotNull( $this->sessionRepository->findByToken('session-token'), ); } public function test_login_returns_bad_request_for_missing_email(): void { $response = $this->controller->login(new Request([ 'password' => 'correct-password', ])); $this->assertSame(400, $response->getStatusCode()); $this->assertSame( ['error' => 'email is required'], json_decode($response->getContent(), true), ); } public function test_login_returns_bad_request_for_missing_password(): void { $response = $this->controller->login(new Request([ 'email' => 'user@example.com', ])); $this->assertSame(400, $response->getStatusCode()); $this->assertSame( ['error' => 'password is required'], json_decode($response->getContent(), true), ); } public function test_login_returns_unauthorized_for_invalid_credentials(): void { $this->createUser('correct-password'); $response = $this->controller->login(new Request([ 'email' => 'user@example.com', 'password' => 'wrong-password', ])); $this->assertSame(401, $response->getStatusCode()); $this->assertSame( ['error' => 'invalid credentials'], json_decode($response->getContent(), true), ); } public function test_logout_deletes_session_and_clears_cookie(): void { $this->createUser('correct-password'); $this->controller->login(new Request([ 'email' => 'user@example.com', 'password' => 'correct-password', ])); $request = new Request; $request->cookies->set( AuthMiddleware::COOKIE_NAME, 'session-token', ); $response = $this->controller->logout($request); $this->assertSame(204, $response->getStatusCode()); $this->assertNull( $this->sessionRepository->findByToken('session-token'), ); $cookies = $response->headers->getCookies(); $this->assertCount(1, $cookies); $this->assertSame( AuthMiddleware::COOKIE_NAME, $cookies[0]->getName(), ); $this->assertSame('', $cookies[0]->getValue()); $this->assertSame(1, $cookies[0]->getExpiresTime()); $this->assertTrue($cookies[0]->isHttpOnly()); $this->assertSame('lax', $cookies[0]->getSameSite()); } private function createUser(string $password): void { $this->userRepository->create(new CreateUserDto( email: new EmailAddress('user@example.com'), passwordHash: $this->passwordHasher->hash($password), )); } }