use FakePasswordHasher in tests to eliminate bcrypt cost
Add a trivial prefix-based PasswordHasher fake and inject it into the three test files that exercise CreateUser or AuthenticateUser. Drops the full phpunit suite from ~7.4s to ~30ms (about 224x) without losing coverage: the round-trip through hash/verify still validates that CreateUser stores something other than the plaintext and that AuthenticateUser only succeeds on a matching hash. CreateUserTest is also refactored to use a setUp method, matching the pattern already used in AuthenticateUserTest and AuthControllerTest.
This commit is contained in:
parent
632085f5b6
commit
bb6bd7cbb3
4 changed files with 76 additions and 39 deletions
20
tests/Fakes/FakePasswordHasher.php
Normal file
20
tests/Fakes/FakePasswordHasher.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Fakes;
|
||||
|
||||
use App\Auth\PasswordHasher;
|
||||
|
||||
class FakePasswordHasher implements PasswordHasher
|
||||
{
|
||||
private const PREFIX = 'hashed:';
|
||||
|
||||
public function hash(string $plaintext): string
|
||||
{
|
||||
return self::PREFIX . $plaintext;
|
||||
}
|
||||
|
||||
public function verify(string $plaintext, string $hash): bool
|
||||
{
|
||||
return $hash === self::PREFIX . $plaintext;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,22 +10,31 @@ use App\User\UseCases\CreateUser;
|
|||
use App\User\UseCases\CreateUserRequest;
|
||||
use App\User\User;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Fakes\FakePasswordHasher;
|
||||
use Tests\Fakes\FakeUserRepository;
|
||||
|
||||
class AuthenticateUserTest extends TestCase
|
||||
{
|
||||
private FakeUserRepository $userRepo;
|
||||
private FakePasswordHasher $passwordHasher;
|
||||
private AuthenticateUser $useCase;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->userRepo = new FakeUserRepository();
|
||||
$createUser = new CreateUser($this->userRepo);
|
||||
$this->passwordHasher = new FakePasswordHasher();
|
||||
$createUser = new CreateUser(
|
||||
$this->userRepo,
|
||||
$this->passwordHasher,
|
||||
);
|
||||
$createUser->execute(new CreateUserRequest(
|
||||
email: 'test@test.com',
|
||||
password: 'password1',
|
||||
));
|
||||
$this->useCase = new AuthenticateUser($this->userRepo);
|
||||
$this->useCase = new AuthenticateUser(
|
||||
$this->userRepo,
|
||||
$this->passwordHasher,
|
||||
);
|
||||
}
|
||||
|
||||
public function test_returns_user_on_valid_credentials(): void
|
||||
|
|
|
|||
|
|
@ -6,67 +6,71 @@ use App\Exceptions\BadRequestException;
|
|||
use App\User\User;
|
||||
use App\User\UseCases\CreateUser;
|
||||
use App\User\UseCases\CreateUserRequest;
|
||||
use Tests\Fakes\FakePasswordHasher;
|
||||
use Tests\Fakes\FakeUserRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CreateUserTest extends TestCase
|
||||
{
|
||||
private FakeUserRepository $userRepo;
|
||||
private FakePasswordHasher $passwordHasher;
|
||||
private CreateUser $useCase;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->userRepo = new FakeUserRepository();
|
||||
$this->passwordHasher = new FakePasswordHasher();
|
||||
$this->useCase = new CreateUser(
|
||||
$this->userRepo,
|
||||
$this->passwordHasher,
|
||||
);
|
||||
}
|
||||
|
||||
public function test_create_user(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$useCase = new CreateUser($userRepo);
|
||||
$useCase->execute(new CreateUserRequest(
|
||||
$this->useCase->execute(new CreateUserRequest(
|
||||
email: 'test@test.com',
|
||||
password: 'password1',
|
||||
));
|
||||
$user = $userRepo->find(0);
|
||||
$user = $this->userRepo->find(0);
|
||||
$this->assertInstanceOf(User::class, $user);
|
||||
$this->assertEquals('test@test.com', $user->getEmail());
|
||||
}
|
||||
|
||||
public function test_throws_if_email_is_null(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$useCase = new CreateUser($userRepo);
|
||||
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage('email is required');
|
||||
|
||||
$useCase->execute(new CreateUserRequest(
|
||||
$this->useCase->execute(new CreateUserRequest(
|
||||
email: null,
|
||||
));
|
||||
}
|
||||
|
||||
public function test_is_admin_defaults_to_false(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$useCase = new CreateUser($userRepo);
|
||||
$useCase->execute(new CreateUserRequest(
|
||||
$this->useCase->execute(new CreateUserRequest(
|
||||
email: 'test@test.com',
|
||||
password: 'password1',
|
||||
));
|
||||
$user = $userRepo->find(0);
|
||||
$user = $this->userRepo->find(0);
|
||||
$this->assertFalse($user->isAdmin());
|
||||
}
|
||||
|
||||
public function test_is_admin_can_be_set_true(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$useCase = new CreateUser($userRepo);
|
||||
$useCase->execute(new CreateUserRequest(
|
||||
$this->useCase->execute(new CreateUserRequest(
|
||||
email: 'test@test.com',
|
||||
password: 'password1',
|
||||
isAdmin: true,
|
||||
));
|
||||
$user = $userRepo->find(0);
|
||||
$user = $this->userRepo->find(0);
|
||||
$this->assertTrue($user->isAdmin());
|
||||
}
|
||||
|
||||
public function test_throws_when_email_already_taken(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$useCase = new CreateUser($userRepo);
|
||||
$useCase->execute(new CreateUserRequest(
|
||||
$this->useCase->execute(new CreateUserRequest(
|
||||
email: 'test@test.com',
|
||||
password: 'password1',
|
||||
));
|
||||
|
|
@ -74,7 +78,7 @@ class CreateUserTest extends TestCase
|
|||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage('email already taken');
|
||||
|
||||
$useCase->execute(new CreateUserRequest(
|
||||
$this->useCase->execute(new CreateUserRequest(
|
||||
email: 'test@test.com',
|
||||
password: 'password1',
|
||||
));
|
||||
|
|
@ -82,13 +86,10 @@ class CreateUserTest extends TestCase
|
|||
|
||||
public function test_throws_if_password_is_null(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$useCase = new CreateUser($userRepo);
|
||||
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage('password is required');
|
||||
|
||||
$useCase->execute(new CreateUserRequest(
|
||||
$this->useCase->execute(new CreateUserRequest(
|
||||
email: 'test@test.com',
|
||||
password: null,
|
||||
));
|
||||
|
|
@ -96,15 +97,12 @@ class CreateUserTest extends TestCase
|
|||
|
||||
public function test_throws_if_password_too_short(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$useCase = new CreateUser($userRepo);
|
||||
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'password must be at least 8 characters'
|
||||
);
|
||||
|
||||
$useCase->execute(new CreateUserRequest(
|
||||
$this->useCase->execute(new CreateUserRequest(
|
||||
email: 'test@test.com',
|
||||
password: 'short',
|
||||
));
|
||||
|
|
@ -112,16 +110,17 @@ class CreateUserTest extends TestCase
|
|||
|
||||
public function test_stores_hashed_password(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$useCase = new CreateUser($userRepo);
|
||||
$useCase->execute(new CreateUserRequest(
|
||||
$this->useCase->execute(new CreateUserRequest(
|
||||
email: 'test@test.com',
|
||||
password: 'password1',
|
||||
));
|
||||
$user = $userRepo->find(0);
|
||||
$user = $this->userRepo->find(0);
|
||||
$this->assertNotEquals('password1', $user->getPasswordHash());
|
||||
$this->assertTrue(
|
||||
password_verify('password1', $user->getPasswordHash())
|
||||
$this->passwordHasher->verify(
|
||||
'password1',
|
||||
$user->getPasswordHash()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
namespace Tests\e2e\Controllers;
|
||||
|
||||
use App\Auth\AuthController;
|
||||
use App\Auth\AuthMiddleware;
|
||||
use App\Auth\CreateSessionDto;
|
||||
use App\Auth\UseCases\CreateSession;
|
||||
use App\User\UseCases\AuthenticateUser;
|
||||
|
|
@ -18,6 +17,7 @@ use Slim\Psr7\Factory\ServerRequestFactory;
|
|||
use Slim\Psr7\Factory\StreamFactory;
|
||||
use Slim\Psr7\Response;
|
||||
use Tests\Fakes\FakeClock;
|
||||
use Tests\Fakes\FakePasswordHasher;
|
||||
use Tests\Fakes\FakeSessionRepository;
|
||||
use Tests\Fakes\FakeTokenGenerator;
|
||||
use Tests\Fakes\FakeUserRepository;
|
||||
|
|
@ -28,6 +28,7 @@ class AuthControllerTest extends TestCase
|
|||
private FakeSessionRepository $sessionRepo;
|
||||
private FakeTokenGenerator $tokenGenerator;
|
||||
private FakeClock $clock;
|
||||
private FakePasswordHasher $passwordHasher;
|
||||
private CreateUser $createUser;
|
||||
private AuthenticateUser $authenticateUser;
|
||||
private CreateSession $createSession;
|
||||
|
|
@ -43,9 +44,16 @@ class AuthControllerTest extends TestCase
|
|||
$this->clock = new FakeClock(
|
||||
new DateTimeImmutable('2025-01-01T12:00:00+00:00')
|
||||
);
|
||||
$this->passwordHasher = new FakePasswordHasher();
|
||||
|
||||
$this->createUser = new CreateUser($this->userRepo);
|
||||
$this->authenticateUser = new AuthenticateUser($this->userRepo);
|
||||
$this->createUser = new CreateUser(
|
||||
$this->userRepo,
|
||||
$this->passwordHasher,
|
||||
);
|
||||
$this->authenticateUser = new AuthenticateUser(
|
||||
$this->userRepo,
|
||||
$this->passwordHasher,
|
||||
);
|
||||
$this->createSession = new CreateSession(
|
||||
$this->sessionRepo,
|
||||
$this->tokenGenerator,
|
||||
|
|
@ -63,7 +71,7 @@ class AuthControllerTest extends TestCase
|
|||
private function makeJsonRequest(
|
||||
string $method,
|
||||
string $path,
|
||||
array $data = [],
|
||||
array $data,
|
||||
): ServerRequestInterface {
|
||||
$body = new StreamFactory()->createStream(json_encode($data));
|
||||
return new ServerRequestFactory()
|
||||
|
|
@ -243,6 +251,7 @@ class AuthControllerTest extends TestCase
|
|||
$request = $this->makeJsonRequest(
|
||||
'POST',
|
||||
'/api/auth/logout',
|
||||
[]
|
||||
)->withCookieParams(['auth_token' => 'existing-session']);
|
||||
|
||||
$response = $this->controller->logout(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue