From 410b752183fc223a28b272a5ff9cb94bc96fe526 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Mon, 18 May 2026 21:36:10 +0300 Subject: [PATCH] add unit tests for user and auth --- backend/tests/Fakes/FakeSessionRepository.php | 47 ++++++++++ backend/tests/Fakes/FakeUserRepository.php | 70 +++++++++++++++ .../Auth/UseCases/AuthenticateUserTest.php | 89 +++++++++++++++++++ .../Unit/Auth/UseCases/CreateSessionTest.php | 51 +++++++++++ .../tests/Unit/Auth/UseCases/LogoutTest.php | 38 ++++++++ backend/tests/Unit/User/UserTest.php | 20 +++++ 6 files changed, 315 insertions(+) create mode 100644 backend/tests/Fakes/FakeSessionRepository.php create mode 100644 backend/tests/Fakes/FakeUserRepository.php create mode 100644 backend/tests/Unit/Auth/UseCases/AuthenticateUserTest.php create mode 100644 backend/tests/Unit/Auth/UseCases/CreateSessionTest.php create mode 100644 backend/tests/Unit/Auth/UseCases/LogoutTest.php create mode 100644 backend/tests/Unit/User/UserTest.php diff --git a/backend/tests/Fakes/FakeSessionRepository.php b/backend/tests/Fakes/FakeSessionRepository.php new file mode 100644 index 0000000..120ccd8 --- /dev/null +++ b/backend/tests/Fakes/FakeSessionRepository.php @@ -0,0 +1,47 @@ +token, + $dto->user, + $dto->createdAt, + $dto->expiresAt + ); + $this->sessionsByToken[$dto->token] = $session; + + return $session; + } + + public function findByToken(string $token): ?Session + { + if (! isset($this->sessionsByToken[$token])) { + return null; + } + + $stored = $this->sessionsByToken[$token]; + + return new Session( + $stored->getToken(), + $stored->getUser(), + $stored->getCreatedAt(), + $stored->getExpiresAt() + ); + } + + public function deleteByToken(string $token): void + { + unset($this->sessionsByToken[$token]); + } +} diff --git a/backend/tests/Fakes/FakeUserRepository.php b/backend/tests/Fakes/FakeUserRepository.php new file mode 100644 index 0000000..dc47606 --- /dev/null +++ b/backend/tests/Fakes/FakeUserRepository.php @@ -0,0 +1,70 @@ +usersById) + 1; + $user = new User($id, $dto->email, $dto->passwordHash); + $this->usersById[$id] = $user; + $this->usersByEmail[$dto->email->value()] = $user; + + return $user; + } + + public function findByEmail(EmailAddress $email): ?User + { + if (! isset($this->usersByEmail[$email->value()])) { + return null; + } + + $stored = $this->usersByEmail[$email->value()]; + + return new User( + $stored->getId(), + $stored->getEmail(), + $stored->getPasswordHash() + ); + } + + public function findByEmailDomain(string $domain): array + { + $result = []; + foreach ($this->usersByEmail as $email => $stored) { + if (str_ends_with($email, '@' . $domain)) { + $result[] = new User( + $stored->getId(), + $stored->getEmail(), + $stored->getPasswordHash() + ); + } + } + + return $result; + } + + public function find(int $id): ?User + { + if (! isset($this->usersById[$id])) { + return null; + } + + $stored = $this->usersById[$id]; + + return new User( + $stored->getId(), + $stored->getEmail(), + $stored->getPasswordHash() + ); + } +} diff --git a/backend/tests/Unit/Auth/UseCases/AuthenticateUserTest.php b/backend/tests/Unit/Auth/UseCases/AuthenticateUserTest.php new file mode 100644 index 0000000..d9056e0 --- /dev/null +++ b/backend/tests/Unit/Auth/UseCases/AuthenticateUserTest.php @@ -0,0 +1,89 @@ +userRepo = new FakeUserRepository(); + $this->hasher = new class implements PasswordHasher { + public function hash(string $password): string + { + return 'hashed-' . $password; + } + + public function verify(string $password, string $hash): bool + { + return $hash === 'hashed-' . $password; + } + }; + + $this->authenticateUser = new AuthenticateUser($this->userRepo, $this->hasher); + } + + public function testAuthenticatesValidUser(): void + { + $email = new EmailAddress('user@example.com'); + $this->userRepo->create(new \App\User\CreateUserDto($email, 'hashed-secret')); + + $request = new AuthenticateUserRequest('user@example.com', 'secret'); + $user = $this->authenticateUser->execute($request); + + $this->assertInstanceOf(User::class, $user); + $this->assertSame('user@example.com', $user->getEmail()->value()); + } + + public function testThrowsWhenEmailMissing(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('email is required'); + + $request = new AuthenticateUserRequest(null, 'secret'); + $this->authenticateUser->execute($request); + } + + public function testThrowsWhenPasswordMissing(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('password is required'); + + $request = new AuthenticateUserRequest('user@example.com', null); + $this->authenticateUser->execute($request); + } + + public function testThrowsWhenUserNotFound(): void + { + $this->expectException(UnauthorizedException::class); + $this->expectExceptionMessage('invalid credentials'); + + $request = new AuthenticateUserRequest('missing@example.com', 'secret'); + $this->authenticateUser->execute($request); + } + + public function testThrowsWhenPasswordIncorrect(): void + { + $email = new EmailAddress('user@example.com'); + $this->userRepo->create(new \App\User\CreateUserDto($email, 'hashed-secret')); + + $this->expectException(UnauthorizedException::class); + $this->expectExceptionMessage('invalid credentials'); + + $request = new AuthenticateUserRequest('user@example.com', 'wrong'); + $this->authenticateUser->execute($request); + } +} diff --git a/backend/tests/Unit/Auth/UseCases/CreateSessionTest.php b/backend/tests/Unit/Auth/UseCases/CreateSessionTest.php new file mode 100644 index 0000000..93c9f25 --- /dev/null +++ b/backend/tests/Unit/Auth/UseCases/CreateSessionTest.php @@ -0,0 +1,51 @@ +sessionRepo = new FakeSessionRepository(); + $this->tokenGenerator = new class implements TokenGenerator { + public function generate(): string + { + return 'fake-token-123'; + } + }; + $this->clock = new class implements Clock { + public function now(): DateTimeImmutable + { + return new DateTimeImmutable('2026-05-18 12:00:00', new \DateTimeZone('UTC')); + } + }; + + $this->createSession = new CreateSession($this->sessionRepo, $this->tokenGenerator, $this->clock); + } + + public function testCreatesSessionForUser(): void + { + $email = new EmailAddress('user@example.com'); + $user = new User(1, $email, 'hashed-password'); + + $session = $this->createSession->execute($user); + + $this->assertSame('fake-token-123', $session->getToken()); + $this->assertSame($user, $session->getUser()); + $this->assertFalse($session->isExpired($this->clock->now())); + } +} diff --git a/backend/tests/Unit/Auth/UseCases/LogoutTest.php b/backend/tests/Unit/Auth/UseCases/LogoutTest.php new file mode 100644 index 0000000..36c5f16 --- /dev/null +++ b/backend/tests/Unit/Auth/UseCases/LogoutTest.php @@ -0,0 +1,38 @@ +sessionRepo = new FakeSessionRepository(); + $this->logout = new Logout($this->sessionRepo); + } + + public function testDeletesSessionByToken(): void + { + $email = new EmailAddress('user@example.com'); + $user = new User(1, $email, 'hashed-password'); + + $session = $this->sessionRepo->create(new \App\Auth\CreateSessionDto( + 'session-token', + $user, + new \DateTimeImmutable(), + new \DateTimeImmutable('+1 hour') + )); + + $this->logout->execute('session-token'); + + $this->assertNull($this->sessionRepo->findByToken('session-token')); + } +} diff --git a/backend/tests/Unit/User/UserTest.php b/backend/tests/Unit/User/UserTest.php new file mode 100644 index 0000000..89734e9 --- /dev/null +++ b/backend/tests/Unit/User/UserTest.php @@ -0,0 +1,20 @@ +assertSame(1, $user->getId()); + $this->assertSame($email, $user->getEmail()); + $this->assertSame('hashed-password', $user->getPasswordHash()); + } +}