test password login

This commit is contained in:
Yisroel Baum 2026-07-31 11:34:50 +03:00
parent 879c294582
commit 93f5f022e4
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9
6 changed files with 220 additions and 0 deletions

View file

@ -101,6 +101,7 @@ class AuthMiddlewareTest extends TestCase
): User { ): User {
$user = app(UserRepository::class)->create(new CreateUserDto( $user = app(UserRepository::class)->create(new CreateUserDto(
email: new EmailAddress('user@example.com'), email: new EmailAddress('user@example.com'),
password: 'correct-password',
)); ));
app(SessionRepository::class)->create(new CreateSessionDto( app(SessionRepository::class)->create(new CreateSessionDto(
token: $token, token: $token,

View file

@ -20,6 +20,7 @@ class EloquentSessionRepositoryTest extends TestCase
{ {
$user = app(UserRepository::class)->create(new CreateUserDto( $user = app(UserRepository::class)->create(new CreateUserDto(
email: new EmailAddress('user@example.com'), email: new EmailAddress('user@example.com'),
password: 'correct-password',
)); ));
$createdAt = $this->utc('2026-07-31T12:00:00'); $createdAt = $this->utc('2026-07-31T12:00:00');
$expiresAt = $this->utc('2026-08-07T12:00:00'); $expiresAt = $this->utc('2026-08-07T12:00:00');
@ -61,6 +62,7 @@ class EloquentSessionRepositoryTest extends TestCase
{ {
$user = app(UserRepository::class)->create(new CreateUserDto( $user = app(UserRepository::class)->create(new CreateUserDto(
email: new EmailAddress('user@example.com'), email: new EmailAddress('user@example.com'),
password: 'correct-password',
)); ));
$repository = app(SessionRepository::class); $repository = app(SessionRepository::class);
$repository->create(new CreateSessionDto( $repository->create(new CreateSessionDto(

View file

@ -0,0 +1,163 @@
<?php
namespace Tests\Feature\Auth;
use App\Auth\Clock;
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
{
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();
$response = $this->postJson('/api/login', [
'email' => ' user@EXAMPLE.COM ',
'password' => 'correct-password',
]);
$response->assertOk()->assertExactJson([
'user' => [
'id' => $user->getId(),
'email' => 'user@example.com',
],
]);
$cookie = $this->findAuthCookie(
$response->headers->getCookies(),
);
$token = $cookie->getValue();
$this->assertMatchesRegularExpression(
'/^[a-f0-9]{64}$/',
$token,
);
$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.');
}
}

View file

@ -25,6 +25,7 @@ class MeEndpointTest extends TestCase
); );
$user = app(UserRepository::class)->create(new CreateUserDto( $user = app(UserRepository::class)->create(new CreateUserDto(
email: new EmailAddress('user@example.com'), email: new EmailAddress('user@example.com'),
password: 'correct-password',
)); ));
app(SessionRepository::class)->create(new CreateSessionDto( app(SessionRepository::class)->create(new CreateSessionDto(
token: 'valid-token', token: 'valid-token',

View file

@ -2,6 +2,8 @@
namespace Tests\Feature\Database; namespace Tests\Feature\Database;
use App\Shared\ValueObject\EmailAddress;
use App\User\UserRepository;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase; use Tests\TestCase;
@ -17,6 +19,17 @@ class DatabaseSeederTest extends TestCase
$this->assertDatabaseHas('users', [ $this->assertDatabaseHas('users', [
'email' => 'user@example.com', 'email' => 'user@example.com',
]); ]);
$this->assertDatabaseMissing('users', [
'email' => 'user@example.com',
'password' => 'password',
]);
$this->assertDatabaseCount('users', 1); $this->assertDatabaseCount('users', 1);
$user = app(UserRepository::class)->findByCredentials(
new EmailAddress('user@example.com'),
'password',
);
$this->assertNotNull($user);
} }
} }

View file

@ -17,6 +17,7 @@ class EloquentUserRepositoryTest extends TestCase
$repository = app(UserRepository::class); $repository = app(UserRepository::class);
$user = $repository->create(new CreateUserDto( $user = $repository->create(new CreateUserDto(
email: new EmailAddress('Founder@EXAMPLE.COM'), email: new EmailAddress('Founder@EXAMPLE.COM'),
password: 'correct-password',
)); ));
$this->assertGreaterThan(0, $user->getId()); $this->assertGreaterThan(0, $user->getId());
@ -28,6 +29,10 @@ class EloquentUserRepositoryTest extends TestCase
'id' => $user->getId(), 'id' => $user->getId(),
'email' => 'Founder@example.com', 'email' => 'Founder@example.com',
]); ]);
$this->assertDatabaseMissing('users', [
'id' => $user->getId(),
'password' => 'correct-password',
]);
$foundUser = $repository->find($user->getId()); $foundUser = $repository->find($user->getId());
@ -45,4 +50,39 @@ class EloquentUserRepositoryTest extends TestCase
$this->assertNull($repository->find(999)); $this->assertNull($repository->find(999));
} }
public function test_it_finds_a_user_with_matching_credentials(): void
{
$repository = app(UserRepository::class);
$createdUser = $repository->create(new CreateUserDto(
email: new EmailAddress('user@example.com'),
password: 'correct-password',
));
$foundUser = $repository->findByCredentials(
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
{
$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(
new EmailAddress('unknown@example.com'),
'correct-password',
));
}
} }