test auth contracts
This commit is contained in:
parent
3da9c586c3
commit
b3266b38c8
15 changed files with 457 additions and 165 deletions
18
backend/tests/Fakes/FakePasswordHasher.php
Normal file
18
backend/tests/Fakes/FakePasswordHasher.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Fakes;
|
||||
|
||||
use App\Auth\PasswordHasher;
|
||||
|
||||
class FakePasswordHasher implements PasswordHasher
|
||||
{
|
||||
public function hash(string $password): string
|
||||
{
|
||||
return 'hashed:'.$password;
|
||||
}
|
||||
|
||||
public function verify(string $password, string $hash): bool
|
||||
{
|
||||
return $this->hash($password) === $hash;
|
||||
}
|
||||
}
|
||||
28
backend/tests/Fakes/FakeTokenGenerator.php
Normal file
28
backend/tests/Fakes/FakeTokenGenerator.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Fakes;
|
||||
|
||||
use App\Auth\TokenGenerator;
|
||||
use RuntimeException;
|
||||
|
||||
class FakeTokenGenerator implements TokenGenerator
|
||||
{
|
||||
private int $callCount = 0;
|
||||
|
||||
/**
|
||||
* @param string[] $tokens
|
||||
*/
|
||||
public function __construct(private array $tokens) {}
|
||||
|
||||
public function generate(): string
|
||||
{
|
||||
if ($this->callCount >= count($this->tokens)) {
|
||||
throw new RuntimeException('FakeTokenGenerator exhausted');
|
||||
}
|
||||
|
||||
$token = $this->tokens[$this->callCount];
|
||||
$this->callCount++;
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
56
backend/tests/Fakes/FakeUserRepository.php
Normal file
56
backend/tests/Fakes/FakeUserRepository.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Fakes;
|
||||
|
||||
use App\Shared\ValueObject\EmailAddress;
|
||||
use App\User\CreateUserDto;
|
||||
use App\User\User;
|
||||
use App\User\UserRepository;
|
||||
|
||||
class FakeUserRepository implements UserRepository
|
||||
{
|
||||
/**
|
||||
* @var array<int, User>
|
||||
*/
|
||||
private array $users = [];
|
||||
|
||||
public function create(CreateUserDto $dto): User
|
||||
{
|
||||
$id = count($this->users) + 1;
|
||||
$user = new User(
|
||||
id: $id,
|
||||
email: $dto->email,
|
||||
passwordHash: $dto->passwordHash,
|
||||
);
|
||||
$this->users[$id] = $user;
|
||||
|
||||
return $this->copy($user);
|
||||
}
|
||||
|
||||
public function find(int $id): ?User
|
||||
{
|
||||
$user = $this->users[$id] ?? null;
|
||||
|
||||
return $user === null ? null : $this->copy($user);
|
||||
}
|
||||
|
||||
public function findByEmail(EmailAddress $email): ?User
|
||||
{
|
||||
foreach ($this->users as $user) {
|
||||
if ($user->getEmail()->value() === $email->value()) {
|
||||
return $this->copy($user);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function copy(User $user): User
|
||||
{
|
||||
return new User(
|
||||
id: $user->getId(),
|
||||
email: $user->getEmail(),
|
||||
passwordHash: $user->getPasswordHash(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ class AuthMiddlewareTest extends TestCase
|
|||
): User {
|
||||
$user = app(UserRepository::class)->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
password: 'correct-password',
|
||||
passwordHash: 'hashed-password',
|
||||
));
|
||||
app(SessionRepository::class)->create(new CreateSessionDto(
|
||||
token: $token,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class EloquentSessionRepositoryTest extends TestCase
|
|||
{
|
||||
$user = app(UserRepository::class)->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
password: 'correct-password',
|
||||
passwordHash: 'hashed-password',
|
||||
));
|
||||
$createdAt = $this->utc('2026-07-31T12:00:00');
|
||||
$expiresAt = $this->utc('2026-08-07T12:00:00');
|
||||
|
|
@ -62,7 +62,7 @@ class EloquentSessionRepositoryTest extends TestCase
|
|||
{
|
||||
$user = app(UserRepository::class)->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
password: 'correct-password',
|
||||
passwordHash: 'hashed-password',
|
||||
));
|
||||
$repository = app(SessionRepository::class);
|
||||
$repository->create(new CreateSessionDto(
|
||||
|
|
|
|||
|
|
@ -2,162 +2,46 @@
|
|||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Auth\Clock;
|
||||
use App\Auth\PasswordHasher;
|
||||
use App\Auth\SessionRepository;
|
||||
use App\Http\Middleware\AuthMiddleware;
|
||||
use App\Shared\ValueObject\EmailAddress;
|
||||
use App\User\CreateUserDto;
|
||||
use App\User\User;
|
||||
use App\User\UserRepository;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Tests\Fakes\FakeClock;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LoginEndpointTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private DateTimeImmutable $currentTime;
|
||||
|
||||
protected function setUp(): void
|
||||
public function test_login_returns_user_and_sets_session_cookie(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->currentTime = new DateTimeImmutable(
|
||||
'2026-07-31T12:00:00',
|
||||
new DateTimeZone('UTC'),
|
||||
);
|
||||
$this->app->instance(
|
||||
Clock::class,
|
||||
new FakeClock($this->currentTime),
|
||||
);
|
||||
config()->set('session.lifetime', 120);
|
||||
config()->set('session.path', '/');
|
||||
config()->set('session.secure', true);
|
||||
config()->set('session.same_site', 'lax');
|
||||
}
|
||||
|
||||
public function test_login_validates_its_request(): void
|
||||
{
|
||||
$this->postJson('/api/login')
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['email', 'password']);
|
||||
|
||||
$this->postJson('/api/login', [
|
||||
'email' => 'invalid-email',
|
||||
'password' => 'password',
|
||||
])->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['email']);
|
||||
}
|
||||
|
||||
public function test_login_rejects_invalid_credentials_generically(): void
|
||||
{
|
||||
$this->createUser();
|
||||
|
||||
$this->postJson('/api/login', [
|
||||
'email' => 'user@example.com',
|
||||
'password' => 'wrong-password',
|
||||
])->assertUnauthorized()
|
||||
->assertExactJson(['error' => 'invalid_credentials'])
|
||||
->assertCookieMissing(AuthMiddleware::COOKIE_NAME);
|
||||
|
||||
$this->postJson('/api/login', [
|
||||
'email' => 'unknown@example.com',
|
||||
'password' => 'correct-password',
|
||||
])->assertUnauthorized()
|
||||
->assertExactJson(['error' => 'invalid_credentials'])
|
||||
->assertCookieMissing(AuthMiddleware::COOKIE_NAME);
|
||||
|
||||
$this->assertDatabaseCount('sessions', 0);
|
||||
}
|
||||
|
||||
public function test_login_creates_a_session_and_returns_the_user(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
$email = 'user@example.com';
|
||||
$password = 'correct-password';
|
||||
$passwordHash = app(PasswordHasher::class)->hash($password);
|
||||
app(UserRepository::class)->create(new CreateUserDto(
|
||||
email: new EmailAddress($email),
|
||||
passwordHash: $passwordHash,
|
||||
));
|
||||
|
||||
$response = $this->postJson('/api/login', [
|
||||
'email' => ' user@EXAMPLE.COM ',
|
||||
'password' => 'correct-password',
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$response->assertOk()->assertExactJson([
|
||||
'user' => [
|
||||
'id' => $user->getId(),
|
||||
'email' => 'user@example.com',
|
||||
],
|
||||
]);
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('user.email', $email);
|
||||
|
||||
$cookie = $this->findAuthCookie(
|
||||
$response->headers->getCookies(),
|
||||
$cookie = $response->getCookie(
|
||||
AuthMiddleware::COOKIE_NAME,
|
||||
false,
|
||||
);
|
||||
$token = $cookie->getValue();
|
||||
|
||||
$this->assertMatchesRegularExpression(
|
||||
'/^[a-f0-9]{64}$/',
|
||||
$token,
|
||||
$this->assertNotNull($cookie);
|
||||
$this->assertNotNull(
|
||||
app(SessionRepository::class)->findByToken(
|
||||
$cookie->getValue(),
|
||||
),
|
||||
);
|
||||
$this->assertTrue($cookie->isHttpOnly());
|
||||
$this->assertTrue($cookie->isSecure());
|
||||
$this->assertSame('/', $cookie->getPath());
|
||||
$this->assertSame('lax', $cookie->getSameSite());
|
||||
$this->assertSame(
|
||||
$this->currentTime->modify('+120 minutes')->getTimestamp(),
|
||||
$cookie->getExpiresTime(),
|
||||
);
|
||||
|
||||
$session = app(SessionRepository::class)->findByToken($token);
|
||||
|
||||
$this->assertNotNull($session);
|
||||
$this->assertSame($user->getId(), $session->getUser()->getId());
|
||||
$this->assertEquals(
|
||||
$this->currentTime,
|
||||
$session->getCreatedAt(),
|
||||
);
|
||||
$this->assertEquals(
|
||||
$this->currentTime->modify('+120 minutes'),
|
||||
$session->getExpiresAt(),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_login_throttles_repeated_attempts(): void
|
||||
{
|
||||
$this->createUser();
|
||||
|
||||
for ($attempt = 1; $attempt <= 5; $attempt++) {
|
||||
$this->postJson('/api/login', [
|
||||
'email' => 'user@example.com',
|
||||
'password' => 'wrong-password',
|
||||
])->assertUnauthorized();
|
||||
}
|
||||
|
||||
$this->postJson('/api/login', [
|
||||
'email' => 'user@example.com',
|
||||
'password' => 'wrong-password',
|
||||
])->assertStatus(429);
|
||||
}
|
||||
|
||||
private function createUser(): User
|
||||
{
|
||||
return app(UserRepository::class)->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
password: 'correct-password',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, Cookie> $cookies
|
||||
*/
|
||||
private function findAuthCookie(array $cookies): Cookie
|
||||
{
|
||||
foreach ($cookies as $cookie) {
|
||||
if ($cookie->getName() === AuthMiddleware::COOKIE_NAME) {
|
||||
return $cookie;
|
||||
}
|
||||
}
|
||||
|
||||
$this->fail('The authentication cookie was not set.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class MeEndpointTest extends TestCase
|
|||
);
|
||||
$user = app(UserRepository::class)->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
password: 'correct-password',
|
||||
passwordHash: 'hashed-password',
|
||||
));
|
||||
app(SessionRepository::class)->create(new CreateSessionDto(
|
||||
token: 'valid-token',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\Feature\Database;
|
||||
|
||||
use App\Auth\PasswordHasher;
|
||||
use App\Shared\ValueObject\EmailAddress;
|
||||
use App\User\UserRepository;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
|
@ -21,15 +22,18 @@ class DatabaseSeederTest extends TestCase
|
|||
]);
|
||||
$this->assertDatabaseMissing('users', [
|
||||
'email' => 'user@example.com',
|
||||
'password' => 'password',
|
||||
'passwordHash' => 'password',
|
||||
]);
|
||||
$this->assertDatabaseCount('users', 1);
|
||||
|
||||
$user = app(UserRepository::class)->findByCredentials(
|
||||
$user = app(UserRepository::class)->findByEmail(
|
||||
new EmailAddress('user@example.com'),
|
||||
'password',
|
||||
);
|
||||
|
||||
$this->assertNotNull($user);
|
||||
$this->assertTrue(app(PasswordHasher::class)->verify(
|
||||
'password',
|
||||
$user->getPasswordHash(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class EloquentUserRepositoryTest extends TestCase
|
|||
$repository = app(UserRepository::class);
|
||||
$user = $repository->create(new CreateUserDto(
|
||||
email: new EmailAddress('Founder@EXAMPLE.COM'),
|
||||
password: 'correct-password',
|
||||
passwordHash: 'hashed-password',
|
||||
));
|
||||
|
||||
$this->assertGreaterThan(0, $user->getId());
|
||||
|
|
@ -29,9 +29,9 @@ class EloquentUserRepositoryTest extends TestCase
|
|||
'id' => $user->getId(),
|
||||
'email' => 'Founder@example.com',
|
||||
]);
|
||||
$this->assertDatabaseMissing('users', [
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->getId(),
|
||||
'password' => 'correct-password',
|
||||
'passwordHash' => 'hashed-password',
|
||||
]);
|
||||
|
||||
$foundUser = $repository->find($user->getId());
|
||||
|
|
@ -42,6 +42,10 @@ class EloquentUserRepositoryTest extends TestCase
|
|||
$user->getEmail()->value(),
|
||||
$foundUser->getEmail()->value(),
|
||||
);
|
||||
$this->assertSame(
|
||||
$user->getPasswordHash(),
|
||||
$foundUser->getPasswordHash(),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_it_returns_null_for_an_unknown_user(): void
|
||||
|
|
@ -51,38 +55,27 @@ class EloquentUserRepositoryTest extends TestCase
|
|||
$this->assertNull($repository->find(999));
|
||||
}
|
||||
|
||||
public function test_it_finds_a_user_with_matching_credentials(): void
|
||||
public function test_it_finds_a_user_by_email(): void
|
||||
{
|
||||
$repository = app(UserRepository::class);
|
||||
$createdUser = $repository->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
password: 'correct-password',
|
||||
passwordHash: 'hashed-password',
|
||||
));
|
||||
|
||||
$foundUser = $repository->findByCredentials(
|
||||
$foundUser = $repository->findByEmail(
|
||||
new EmailAddress('user@EXAMPLE.COM'),
|
||||
'correct-password',
|
||||
);
|
||||
|
||||
$this->assertNotNull($foundUser);
|
||||
$this->assertSame($createdUser->getId(), $foundUser->getId());
|
||||
}
|
||||
|
||||
public function test_it_rejects_non_matching_credentials(): void
|
||||
public function test_it_returns_null_for_an_unknown_email(): void
|
||||
{
|
||||
$repository = app(UserRepository::class);
|
||||
$repository->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
password: 'correct-password',
|
||||
));
|
||||
|
||||
$this->assertNull($repository->findByCredentials(
|
||||
new EmailAddress('user@example.com'),
|
||||
'wrong-password',
|
||||
));
|
||||
$this->assertNull($repository->findByCredentials(
|
||||
$this->assertNull($repository->findByEmail(
|
||||
new EmailAddress('unknown@example.com'),
|
||||
'correct-password',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ class AuthMiddlewareTest extends TestCase
|
|||
return new User(
|
||||
id: 7,
|
||||
email: new EmailAddress('user@example.com'),
|
||||
passwordHash: 'hashed-password',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class SessionTest extends TestCase
|
|||
$user = new User(
|
||||
id: 7,
|
||||
email: new EmailAddress('user@example.com'),
|
||||
passwordHash: 'hashed-password',
|
||||
);
|
||||
$createdAt = $this->utc('2026-07-31T12:00:00');
|
||||
$expiresAt = $this->utc('2026-08-07T12:00:00');
|
||||
|
|
@ -40,6 +41,7 @@ class SessionTest extends TestCase
|
|||
user: new User(
|
||||
id: 7,
|
||||
email: new EmailAddress('user@example.com'),
|
||||
passwordHash: 'hashed-password',
|
||||
),
|
||||
createdAt: $this->utc('2026-07-31T12:00:00'),
|
||||
expiresAt: $expiresAt,
|
||||
|
|
|
|||
104
backend/tests/Unit/Auth/UseCases/AuthenticateUserTest.php
Normal file
104
backend/tests/Unit/Auth/UseCases/AuthenticateUserTest.php
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Auth\UseCases;
|
||||
|
||||
use App\Auth\UseCases\AuthenticateUser\AuthenticateUser;
|
||||
use App\Auth\UseCases\AuthenticateUser\AuthenticateUserRequest;
|
||||
use App\Exceptions\BadRequestException;
|
||||
use App\Exceptions\UnauthorizedException;
|
||||
use App\Shared\ValueObject\EmailAddress;
|
||||
use App\User\CreateUserDto;
|
||||
use App\User\User;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Fakes\FakePasswordHasher;
|
||||
use Tests\Fakes\FakeUserRepository;
|
||||
|
||||
class AuthenticateUserTest extends TestCase
|
||||
{
|
||||
private FakeUserRepository $userRepository;
|
||||
|
||||
private FakePasswordHasher $passwordHasher;
|
||||
|
||||
private AuthenticateUser $useCase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->userRepository = new FakeUserRepository;
|
||||
$this->passwordHasher = new FakePasswordHasher;
|
||||
$this->useCase = new AuthenticateUser(
|
||||
$this->userRepository,
|
||||
$this->passwordHasher,
|
||||
);
|
||||
}
|
||||
|
||||
public function test_null_email_throws_bad_request(): void
|
||||
{
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage('email is required');
|
||||
|
||||
$this->useCase->execute(new AuthenticateUserRequest(
|
||||
email: null,
|
||||
password: 'correct-password',
|
||||
));
|
||||
}
|
||||
|
||||
public function test_null_password_throws_bad_request(): void
|
||||
{
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage('password is required');
|
||||
|
||||
$this->useCase->execute(new AuthenticateUserRequest(
|
||||
email: 'user@example.com',
|
||||
password: null,
|
||||
));
|
||||
}
|
||||
|
||||
public function test_unknown_email_throws_unauthorized(): void
|
||||
{
|
||||
$this->expectException(UnauthorizedException::class);
|
||||
$this->expectExceptionMessage('invalid credentials');
|
||||
|
||||
$this->useCase->execute(new AuthenticateUserRequest(
|
||||
email: 'unknown@example.com',
|
||||
password: 'correct-password',
|
||||
));
|
||||
}
|
||||
|
||||
public function test_wrong_password_throws_unauthorized(): void
|
||||
{
|
||||
$this->createUser('correct-password');
|
||||
$this->expectException(UnauthorizedException::class);
|
||||
$this->expectExceptionMessage('invalid credentials');
|
||||
|
||||
$this->useCase->execute(new AuthenticateUserRequest(
|
||||
email: 'user@example.com',
|
||||
password: 'wrong-password',
|
||||
));
|
||||
}
|
||||
|
||||
public function test_valid_credentials_return_user(): void
|
||||
{
|
||||
$user = $this->createUser('correct-password');
|
||||
|
||||
$authenticatedUser = $this->useCase->execute(
|
||||
new AuthenticateUserRequest(
|
||||
email: 'user@example.com',
|
||||
password: 'correct-password',
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame($user->getId(), $authenticatedUser->getId());
|
||||
$this->assertSame(
|
||||
$user->getEmail()->value(),
|
||||
$authenticatedUser->getEmail()->value(),
|
||||
);
|
||||
}
|
||||
|
||||
private function createUser(string $password): User
|
||||
{
|
||||
return $this->userRepository->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
passwordHash: $this->passwordHasher->hash($password),
|
||||
));
|
||||
}
|
||||
}
|
||||
70
backend/tests/Unit/Auth/UseCases/CreateSessionTest.php
Normal file
70
backend/tests/Unit/Auth/UseCases/CreateSessionTest.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Auth\UseCases;
|
||||
|
||||
use App\Auth\UseCases\CreateSession\CreateSession;
|
||||
use App\Shared\ValueObject\EmailAddress;
|
||||
use App\User\User;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Fakes\FakeClock;
|
||||
use Tests\Fakes\FakeSessionRepository;
|
||||
use Tests\Fakes\FakeTokenGenerator;
|
||||
|
||||
class CreateSessionTest extends TestCase
|
||||
{
|
||||
private DateTimeImmutable $now;
|
||||
|
||||
private FakeSessionRepository $sessionRepository;
|
||||
|
||||
private CreateSession $useCase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->now = new DateTimeImmutable(
|
||||
'2026-07-31T12:00:00',
|
||||
new DateTimeZone('UTC'),
|
||||
);
|
||||
$this->sessionRepository = new FakeSessionRepository;
|
||||
$this->useCase = new CreateSession(
|
||||
$this->sessionRepository,
|
||||
new FakeTokenGenerator(['session-token']),
|
||||
new FakeClock($this->now),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_creates_a_seven_day_session_with_generated_token(): void
|
||||
{
|
||||
$user = $this->user();
|
||||
|
||||
$session = $this->useCase->execute($user);
|
||||
|
||||
$this->assertSame('session-token', $session->getToken());
|
||||
$this->assertSame($user, $session->getUser());
|
||||
$this->assertSame($this->now, $session->getCreatedAt());
|
||||
$this->assertEquals(
|
||||
$this->now->modify('+7 days'),
|
||||
$session->getExpiresAt(),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_created_session_is_findable_by_token(): void
|
||||
{
|
||||
$this->useCase->execute($this->user());
|
||||
|
||||
$session = $this->sessionRepository->findByToken('session-token');
|
||||
|
||||
$this->assertNotNull($session);
|
||||
$this->assertSame(7, $session->getUser()->getId());
|
||||
}
|
||||
|
||||
private function user(): User
|
||||
{
|
||||
return new User(
|
||||
id: 7,
|
||||
email: new EmailAddress('user@example.com'),
|
||||
passwordHash: 'hashed:correct-password',
|
||||
);
|
||||
}
|
||||
}
|
||||
127
backend/tests/Unit/Http/Controllers/AuthControllerTest.php
Normal file
127
backend/tests/Unit/Http/Controllers/AuthControllerTest.php
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Http\Controllers;
|
||||
|
||||
use App\Auth\UseCases\AuthenticateUser\AuthenticateUser;
|
||||
use App\Auth\UseCases\CreateSession\CreateSession;
|
||||
use App\Http\Controllers\AuthController;
|
||||
use App\Http\Middleware\AuthMiddleware;
|
||||
use App\Shared\ValueObject\EmailAddress;
|
||||
use App\User\CreateUserDto;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Http\Request;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Fakes\FakeClock;
|
||||
use Tests\Fakes\FakePasswordHasher;
|
||||
use Tests\Fakes\FakeSessionRepository;
|
||||
use Tests\Fakes\FakeTokenGenerator;
|
||||
use Tests\Fakes\FakeUserRepository;
|
||||
|
||||
class AuthControllerTest extends TestCase
|
||||
{
|
||||
private FakeUserRepository $userRepository;
|
||||
|
||||
private FakePasswordHasher $passwordHasher;
|
||||
|
||||
private FakeSessionRepository $sessionRepository;
|
||||
|
||||
private AuthController $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->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'),
|
||||
)),
|
||||
);
|
||||
$this->controller = new AuthController(
|
||||
$authenticateUser,
|
||||
$createSession,
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
private function createUser(string $password): void
|
||||
{
|
||||
$this->userRepository->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
passwordHash: $this->passwordHasher->hash($password),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -8,12 +8,17 @@ use PHPUnit\Framework\TestCase;
|
|||
|
||||
class UserTest extends TestCase
|
||||
{
|
||||
public function test_it_exposes_its_identity_and_email(): void
|
||||
public function test_it_exposes_its_identity_email_and_password_hash(): void
|
||||
{
|
||||
$email = new EmailAddress('user@example.com');
|
||||
$user = new User(id: 42, email: $email);
|
||||
$user = new User(
|
||||
id: 42,
|
||||
email: $email,
|
||||
passwordHash: 'hashed-password',
|
||||
);
|
||||
|
||||
$this->assertSame(42, $user->getId());
|
||||
$this->assertSame($email, $user->getEmail());
|
||||
$this->assertSame('hashed-password', $user->getPasswordHash());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue