Commit d79af452 authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Merge branch '25-write-users-authenticate-tests' into 'master'

Resolve "Write Users Authenticate tests"

Closes #25

See merge request kretschmar/doorcode!19
parents 4bc3f161 5ecd6acb
Pipeline #2611 passed with stages
in 1 minute and 46 seconds
......@@ -30,6 +30,7 @@
<exclude>
<directory suffix=".php">./app/Console</directory>
<directory suffix="ServiceProvider.php">./src</directory>
<directory prefix="Local" suffix="Repository.php">./src</directory>
</exclude>
</whitelist>
</filter>
......
......@@ -124,7 +124,7 @@ class Token
}
/**
* @return Carbon|null
* @return \Carbon\Carbon|null
*/
public function getExpiresAt(): ?Carbon
{
......@@ -132,7 +132,7 @@ class Token
}
/**
* @return Carbon|null
* @return \Carbon\Carbon|null
*/
public function getCreatedAt(): ?Carbon
{
......@@ -140,13 +140,21 @@ class Token
}
/**
* @return Carbon|null
* @return \Carbon\Carbon|null
*/
public function getUpdatedAt(): ?Carbon
{
return $this->updatedAt;
}
/**
* @param \Carbon\Carbon|null $expires
*/
public function setExpiresAt(?Carbon $expires): void
{
$this->expiresAt = $expires;
}
/**
* @param Carbon $date
* @return bool
......@@ -155,4 +163,20 @@ class Token
{
return $this->expiresAt === null || $this->expiresAt->isAfter($date);
}
/**
* @return bool
*/
public function isValid(): bool
{
return $this->isValidAtTime(Carbon::now());
}
/**
* @return bool
*/
public function isInvalid(): bool
{
return !$this->isValid();
}
}
......@@ -7,9 +7,9 @@ use Source\Entities\SamlUser;
class InMemorySamlRepository implements SamlRepository
{
protected ?SamlUser $userToLogInAs;
protected ?SamlUser $userToLogInAs = null;
protected ?SamlUser $loggedInUser;
protected ?SamlUser $loggedInUser = null;
protected string $loginUrl;
......@@ -23,7 +23,7 @@ class InMemorySamlRepository implements SamlRepository
$this->logoutUrl = $logoutUrl;
}
public function setLoginUser(SamlUser $user): void
public function setLoginUser(?SamlUser $user): void
{
$this->userToLogInAs = $user;
}
......
......@@ -24,6 +24,14 @@ class InMemoryTokensRepository implements TokensRepository
return $token;
}
/**
* @return Token[]
*/
public function all(): array
{
return $this->tokens;
}
/** @inheritDoc */
public function findValidToken(string $tokenToMatch): ?Token
......@@ -42,8 +50,10 @@ class InMemoryTokensRepository implements TokensRepository
*/
public function invalidateToken(string $token): void
{
$this->tokens = array_filter($this->tokens, static function (Token $t) use ($token) {
return !$t->matches($token);
});
$tok = $this->findValidToken($token);
if ($tok) {
$tok->setExpiresAt(Carbon::now());
}
}
}
......@@ -13,12 +13,26 @@ use Source\Exceptions\AuthenticationException;
class Authenticate implements AuthenticateUseCase
{
/**
* @var \Source\Gateways\Users\UsersRepository
*/
protected UsersRepository $users;
/**
* @var \Source\Gateways\Tokens\TokensRepository
*/
protected TokensRepository $tokens;
/**
* @var \Source\Gateways\Saml\SamlRepository
*/
protected SamlRepository $saml;
/**
* @param \Source\Gateways\Users\UsersRepository $users
* @param \Source\Gateways\Saml\SamlRepository $saml
* @param \Source\Gateways\Tokens\TokensRepository $tokens
*/
public function __construct(UsersRepository $users, SamlRepository $saml, TokensRepository $tokens)
{
$this->saml = $saml;
......@@ -59,6 +73,9 @@ class Authenticate implements AuthenticateUseCase
$presenter->present($response);
}
/**
* @inheritDoc
*/
public function handToSaml(array $options = []): string
{
return $this->saml->login($options);
......@@ -69,25 +86,25 @@ class Authenticate implements AuthenticateUseCase
*/
public function handleSamlLogin(Presenter $presenter): void
{
$user = $this->saml->handleLogin();
$samlUser = $this->saml->handleLogin();
if (!$user) {
if (!$samlUser) {
throw new UserCreationException();
}
// First check to see if the user exists in the database.
$user = $this->users->findByEmail($user->getEmail());
$user = $this->users->findByEmail($samlUser->getEmail());
// If the user does not exist, create them.
if (!$user) {
$user = $this->users->create(
new User(
0,
$user->getFirstName(),
$user->getLastName(),
$user->getDisplayName(),
$user->getEmplid(),
$user->getEmail(),
$samlUser->getFirstName(),
$samlUser->getLastName(),
$samlUser->getDisplayName(),
$samlUser->getEmplid(),
$samlUser->getEmail(),
null,
null
)
......
<?php
namespace Tests\Unit\Source\UseCases\Users\Authenticate;
use Carbon\Carbon;
use Source\Entities\User;
use Source\Exceptions\AuthenticationException;
class AttemptUseCaseTest extends UseCaseBaseTest
{
protected const VALID_EMAIL = 'email';
protected const VALID_PASS = 'password';
/**
* @param array $credentials
* @throws \Source\Exceptions\AuthenticationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
protected function handleTest(
array $credentials = [
'email' => self::VALID_EMAIL,
'password' => self::VALID_PASS,
]
): void {
$this->useCase->attempt($this->presenter, $credentials);
$this->response = $this->presenter->response;
}
/**
* @return \Source\Entities\User
*/
protected function createUser(): User
{
return $this->users->create(new User(0, '', '', '', '', self::VALID_EMAIL, self::VALID_PASS, ''));
}
/**
* @test
* @throws \Source\Exceptions\AuthenticationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_calls_present_on_presenter(): void
{
$this->createUser();
$this->handleTest([
'email' => self::VALID_EMAIL,
'password' => self::VALID_PASS,
]);
$this->assertTrue($this->presenter->wasPresenterCalled());
}
public function attemptAuthenticationProvider(): array
{
return [
[[]],
[['email' => 'eem',]],
[['password' => 'pass']],
[['email' => self::VALID_EMAIL, 'password' => 'ree']],
[['email' => 'non-existent', 'password' => self::VALID_PASS]],
];
}
/**
* @test
* @dataProvider attemptAuthenticationProvider
* @param array $credentials
* @throws \Source\Exceptions\AuthenticationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_cannot_find_users_for_invalid_input(array $credentials): void
{
$this->createUser();
$this->expectException(AuthenticationException::class);
$this->handleTest($credentials);
}
/**
* @test
* @throws \Source\Exceptions\AuthenticationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_creates_a_token_for_the_user(): void
{
$user = $this->createUser();
$this->handleTest();
$this->assertCount(1, $this->tokens->all());
$token = $this->tokens->all()[0];
$this->assertLessThan(Carbon::now()->addDays(2), $token->getExpiresAt());
$this->assertGreaterThan(Carbon::now()->addDays(1), $token->getExpiresAt());
$this->assertEquals($user->getId(), $token->getUserId());
$this->assertEquals(60, strlen($token->getTokenString()));
$this->assertEquals($token, $this->response->getToken());
}
/**
* @test
* @throws \Source\Exceptions\AuthenticationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_authenticates_a_user(): void
{
$user = $this->createUser();
$this->handleTest();
$this->assertEquals($user, $this->response->getUser());
}
}
<?php
namespace Tests\Unit\Source\UseCases\Users\Authenticate;
use Source\UseCases\Users\Authenticate\Presenter;
use Source\UseCases\Users\Authenticate\ResponseModel;
class PresenterStub implements Presenter
{
public ResponseModel $response;
protected bool $presenterCalled = false;
public function present(ResponseModel $responseModel): void
{
$this->presenterCalled = true;
$this->response = $responseModel;
}
public function wasPresenterCalled(): bool
{
return $this->presenterCalled;
}
public function getViewModel(): array
{
return [];
}
}
<?php
namespace Tests\Unit\Source\UseCases\Users\Authenticate;
use Carbon\Carbon;
use Source\Entities\User;
use Source\Entities\Token;
use PHPUnit\Framework\TestCase;
use Source\UseCases\Users\Authenticate\APIPresenter;
use Source\UseCases\Users\Authenticate\ResponseModel;
class PresenterTest extends TestCase
{
/**
* @var \Source\UseCases\Users\Authenticate\APIPresenter
*/
protected APIPresenter $presenter;
/**
* @var \Source\UseCases\Users\Authenticate\ResponseModel
*/
protected ResponseModel $model;
/**
* @var array
*/
protected array $response;
public function setUp(): void
{
parent::setUp();
$this->presenter = new APIPresenter();
}
/**
* @param \Source\Entities\User $user
* @param \Source\Entities\Token $token
*/
public function handleTest(User $user, Token $token): void
{
$this->model = new ResponseModel($user, $token);
$this->presenter->present($this->model);
$this->response = $this->presenter->getViewModel();
}
/**
* @test
* @throws \Exception
*/
public function it_presents_the_token(): void
{
$expires = new Carbon('2020-03-04');
$user = new User(
0,
'first',
'last',
'display',
'emplid',
'email',
'password',
'doorcode',
$expires,
null,
null
);
$token = new Token(0, 0, 'token', 'nomen', $expires);
$this->handleTest($user, $token);
$minutes = $expires->diffInMinutes(Carbon::now());
$this->assertEquals([
'value' => 'token',
'expires_at' => '2020-03-04T00:00:00+00:00',
'minutes' => $minutes,
], $this->response['token']);
}
/**
* @test
*/
public function it_presents_the_token_with_no_expire(): void
{
$expires = null;
$user = new User(
0,
'first',
'last',
'display',
'emplid',
'email',
'password',
'doorcode',
$expires,
null,
null
);
$token = new Token(0, 0, 'token', 'nomen', $expires);
$this->handleTest($user, $token);
$this->assertEquals([
'value' => 'token',
'expires_at' => null,
'minutes' => 0,
], $this->response['token']);
}
/** @test */
public function it_formats_a_user(): void
{
$user = new User(
0,
'first',
'last',
'display',
'emplid',
'email',
'password',
'doorcode',
new Carbon('2020-02-02'),
null,
null
);
$token = new Token(0, 0, 'token', 'nomen', new Carbon('2020-03-04'));
$this->handleTest($user, $token);
$this->assertEquals([
'id' => 0,
'first_name' => 'first',
'last_name' => 'last',
'display_name' => 'display',
'emplid' => 'emplid',
'email' => 'email',
'expires_at' => '2020-02-02T00:00:00+00:00',
'created_at' => null,
'updated_at' => null,
], $this->response['user']);
}
}
<?php
namespace Tests\Unit\Source\UseCases\Users\Authenticate;
use Carbon\Carbon;
use Source\Entities\User;
use Source\Entities\Token;
use Source\Entities\SamlUser;
use Tests\Doubles\InMemoryUsersRepositoryStub;
use Source\UseCases\Users\Authenticate\Authenticate;
use Source\UseCases\Users\Authenticate\UserCreationException;
class SamlUseCaseTest extends UseCaseBaseTest
{
protected const VALID_EMAIL = 'email';
/**
* @param \Source\Entities\SamlUser|null $samlUser
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\UseCases\Users\Authenticate\UserCreationException
*/
protected function handleLoginTest(?SamlUser $samlUser = null): void
{
$this->saml->setLoginUser($samlUser);
$this->saml->login();
$this->useCase->handleSamlLogin($this->presenter);
$this->response = $this->presenter->response;
}
/**
* @return \Source\Entities\User
*/
protected function createUser(): User
{
return $this->users->create(new User(0, '', '', '', '', self::VALID_EMAIL, '', ''));
}
/**
* @return \Source\Entities\SamlUser
*/
protected function createSamlUser(): SamlUser
{
return new SamlUser('first', 'last', 'emplid', strtoupper(self::VALID_EMAIL));
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\UseCases\Users\Authenticate\UserCreationException
*/
public function it_calls_present_on_presenter(): void
{
$samlUser = $this->createSamlUser();
$this->handleLoginTest($samlUser);
$this->assertTrue($this->presenter->wasPresenterCalled());
}
/**
* @test
*/
public function it_returns_saml_login_link(): void
{
$link = $this->useCase->handToSaml();
$this->assertEquals($this->loginUrl, $link);
}
/**
* @test
*/
public function it_returns_saml_logout_link(): void
{
$link = $this->useCase->samlLogout(null);
$this->assertEquals($this->logoutUrl, $link);
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_invalidates_token_on_saml_logout(): void
{
$user = $this->createUser();
$this->tokens->create(new Token(0, $user->getId(), 'token'));
$this->useCase->samlLogout('token');
$tok = $this->tokens->all()[0];
$this->assertTrue($tok->isInvalid());
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\UseCases\Users\Authenticate\UserCreationException
*/
public function it_cannot_create_null_users(): void
{</