Commit 04cf7431 authored by Jacob Priddy's avatar Jacob Priddy 👌

More tests and some slight refactoring

parent 617136ac
Pipeline #2790 canceled with stages
in 1 minute and 35 seconds
......@@ -44,21 +44,15 @@ abstract class ApiController extends Controller
*/
public function respondWithData(array $data): JsonResponse
{
return response()->json(
array_merge(
$data,
[
'status' => 'success',
'code' => $this->status,
]
),
$this->status
);
return new JsonResponse(array_merge($data, [
'status' => 'success',
'code' => $this->status,
]), $this->status);
}
public function respondWithMessage(string $message): JsonResponse
{
return response()->json([
return new JsonResponse([
'message' => $message,
'status' => 'success',
'code' => $this->status,
......@@ -71,7 +65,7 @@ abstract class ApiController extends Controller
*/
public function respondWithError(string $message): JsonResponse
{
return response()->json([
return new JsonResponse([
'status' => 'error',
'code' => $this->status,
'message' => $message,
......
......@@ -5,8 +5,8 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Cookie\CookieJar;
use Illuminate\Http\JsonResponse;
use Source\Authorization\Authorizer;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Cookie;
use Source\Exceptions\AuthenticationException;
use Source\Exceptions\EntityNotFoundException;
use Source\UseCases\Users\Authenticate\APIPresenter;
......@@ -15,13 +15,17 @@ use Source\UseCases\Users\Authenticate\UserCreationException;
class AuthController extends ApiController
{
protected Request $request;
protected CookieJar $cookieJar;
public function __construct(Request $request, CookieJar $cookieJar)
/**
* @param \Illuminate\Http\Request $request
* @param \Source\Authorization\Authorizer $authorizer
* @param \Illuminate\Cookie\CookieJar $cookieJar
*/
public function __construct(Request $request, Authorizer $authorizer, CookieJar $cookieJar)
{
$this->request = $request;
parent::__construct($request, $authorizer);
$this->cookieJar = $cookieJar;
}
......@@ -38,7 +42,7 @@ class AuthController extends ApiController
$authenticateUseCase->attempt($presenter, $this->request->all());
return $this->respondWithData($presenter->getViewModel())->withCookie(
cookie(
$this->cookieJar->make(
'api_token',
$presenter->getViewModel()['token']['value'],
$presenter->getViewModel()['token']['minutes']
......@@ -46,6 +50,10 @@ class AuthController extends ApiController
);
}
/**
* @param \Source\UseCases\Users\Authenticate\AuthenticateUseCase $authenticateUseCase
* @return \Illuminate\Http\RedirectResponse
*/
public function samlLogin(AuthenticateUseCase $authenticateUseCase): RedirectResponse
{
return redirect()->to($authenticateUseCase->handToSaml());
......@@ -68,10 +76,12 @@ class AuthController extends ApiController
);
}
return redirect()->intended(url(config('saml.home_page')))->cookie(
'api_token',
$presenter->getViewModel()['token']['value'],
$presenter->getViewModel()['token']['minutes']
return redirect()->intended(url(config('saml.home_page')))->withCookie(
$this->cookieJar->make(
'api_token',
$presenter->getViewModel()['token']['value'],
$presenter->getViewModel()['token']['minutes']
)
);
}
......@@ -81,7 +91,7 @@ class AuthController extends ApiController
*/
public function samlLogout(AuthenticateUseCase $authenticateUseCase): RedirectResponse
{
Cookie::queue($this->cookieJar->forget('api_token'));
$this->cookieJar->queue($this->cookieJar->forget('api_token'));
return redirect()->to(
$authenticateUseCase->samlLogout(
......
......@@ -123,6 +123,10 @@ class SimpleSamlPhpSamlRepository implements SamlRepository
*/
public function logout(): string
{
return $this->saml->getLogoutURL($this->logoutUrl);
if ($this->isAuthenticated()) {
return $this->saml->getLogoutURL($this->logoutUrl);
}
return $this->logoutUrl;
}
}
<?php
namespace Tests\Feature\Api\Auth;
use Tests\TestCase;
use Source\Entities\User;
use Source\Entities\Token;
use Source\UseCases\Users\Authenticate\AuthenticateUseCase;
class AuthControllerTest extends TestCase
{
/**
* @var \Tests\Feature\Api\Auth\UserAuthenticateUseCaseStub
*/
protected UserAuthenticateUseCaseStub $useCase;
public function setUp(): void
{
parent::setUp();
$this->useCase = new UserAuthenticateUseCaseStub();
$this->app->bind(AuthenticateUseCase::class, function () {
return $this->useCase;
});
}
/**
* @test
*/
public function it_tests_application_login(): void
{
$this->useCase->setUserAndToken(
new User(1, 'Tea', '', '', '', '', '', ''),
new Token(1, 1, 'token_string')
);
$response = $this->postJson('/login', ['Hello' => 'There', 'General', 'Kenobi']);
$response->assertStatus(200);
$this->assertEquals(['Hello' => 'There', 'General', 'Kenobi'], $this->useCase->getAttemptedCredentials());
$response->assertCookie('api_token', 'token_string');
$response->assertJsonFragment(['first_name' => 'Tea']);
}
/**
* @test
*/
public function it_tests_saml_redirection(): void
{
$this->useCase->setSamlUrl('im dyin bro');
$response = $this->get('/login');
$response->assertStatus(302);
$response->assertRedirect('im dyin bro');
}
/**
* @test
*/
public function it_handles_saml_callback_when_exception_is_thrown(): void
{
$this->useCase->throwCreationException = true;
$response = $this->get('/handle-login');
$response->assertStatus(200);
$response->assertJson(['status' => 'error', 'message' => 'There was an error authenticating the user. Please contact an administrator.']);
}
/**
* @test
*/
public function it_handles_saml_callback(): void
{
$this->useCase->setUserAndToken(
new User(1, 'Tea', '', '', '', '', '', ''),
new Token(1, 1, 'token_string')
);
$response = $this->get('/handle-login');
$response->assertCookie('api_token', 'token_string');
$response->assertStatus(302);
}
/**
* @test
*/
public function it_logs_the_user_out_and_expires_cookie(): void
{
$this->useCase->setSamlUrl('I need to go to work rn tbh');
$response = $this->withCookie('api_token', 'token')->get('/logout');
$response->assertStatus(302);
$response->assertRedirect('I need to go to work rn tbh');
$response->assertCookieExpired('api_token');
}
}
<?php
namespace Tests\Feature\Api\Auth;
use Source\Entities\User;
use Source\Entities\Token;
use Source\UseCases\Users\Authenticate\Presenter;
use Source\UseCases\Users\Authenticate\ResponseModel;
use Source\UseCases\Users\Authenticate\AuthenticateUseCase;
use Source\UseCases\Users\Authenticate\UserCreationException;
class UserAuthenticateUseCaseStub implements AuthenticateUseCase
{
/**
* @var \Source\Entities\User
*/
protected User $user;
/**
* @var \Source\Entities\Token
*/
protected Token $token;
/**
* @var array
*/
protected array $credentials = [];
protected string $samlUrl = '';
public ?string $tokenString = null;
public bool $throwCreationException = false;
/**
* @param \Source\Entities\User $user
* @param \Source\Entities\Token $token
*/
public function setUserAndToken(User $user, Token $token): void
{
$this->user = $user;
$this->token = $token;
}
public function setSamlUrl(string $url): void
{
$this->samlUrl = $url;
}
public function getAttemptedCredentials(): array
{
return $this->credentials;
}
/**
* @inheritDoc
*/
public function attempt(Presenter $presenter, array $credentials): void
{
$this->credentials = $credentials;
$response = new ResponseModel($this->user, $this->token);
$presenter->present($response);
}
/**
* @inheritDoc
*/
public function samlLogout(?string $token): string
{
$this->tokenString = $token;
return $this->samlUrl;
}
/**
* @inheritDoc
*/
public function handToSaml(array $options = []): string
{
return $this->samlUrl;
}
/**
* @inheritDoc
*/
public function handleSamlLogin(Presenter $presenter): void
{
if ($this->throwCreationException) {
throw new UserCreationException();
}
$response = new ResponseModel($this->user, $this->token);
$presenter->present($response);
}
}
......@@ -7,6 +7,7 @@ use Source\Entities\Group;
use Source\Gateways\Groups\GroupsRepository;
use Source\Exceptions\EntityNotFoundException;
use Illuminate\Foundation\Testing\TestResponse;
use Tests\Doubles\InMemoryGroupsRepositoryStub;
use Tests\Feature\AuthenticatesWithApplicationTestCase;
class UpdateGroupApiTest extends AuthenticatesWithApplicationTestCase
......@@ -126,7 +127,7 @@ class UpdateGroupApiTest extends AuthenticatesWithApplicationTestCase
* @test
* @throws EntityNotFoundException
*/
public function it_cannot_update_non_existient_group(): void
public function it_cannot_update_non_existent_group(): void
{
$this->authenticate();
$this->handleTest('asdf', [
......@@ -136,4 +137,27 @@ class UpdateGroupApiTest extends AuthenticatesWithApplicationTestCase
$this->response->assertStatus(404);
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_presents_error(): void
{
$groups = new InMemoryGroupsRepositoryStub();
$groups->setGroupToReturnOnGet(new Group(69, 'whee', 'asdf'));
$this->app->bind(GroupsRepository::class, static function () use (&$groups) {
return $groups;
});
$this->authenticate();
$this->handleTest('69', [
'title' => 'new title',
'description' => 'new desc',
]);
$this->response->assertStatus(200);
$this->response->assertJson(['status' => 'error', 'message' => 'Unable to update group.']);
}
}
......@@ -4,7 +4,9 @@
namespace Tests\Feature\Api\Users;
use Source\Entities\User;
use Source\Gateways\Users\UsersRepository;
use Source\Exceptions\EntityNotFoundException;
use Tests\Doubles\InMemoryUsersRepositoryStub;
use Illuminate\Foundation\Testing\TestResponse;
use Tests\Feature\AuthenticatesWithApplicationTestCase;
......@@ -55,32 +57,32 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// Missing one of the required
[
[
'last_name' => $user->getLastName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
'email' => $user->getEmail(),
],
['first_name' => ['The first name field is required.']],
],
[
[
'first_name' => $user->getFirstName(),
'first_name' => $user->getFirstName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
'email' => $user->getEmail(),
],
['last_name' => ['The last name field is required.']],
],
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'email' => $user->getEmail(),
'last_name' => $user->getLastName(),
'email' => $user->getEmail(),
],
['display_name' => ['The display name field is required.']],
],
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
],
['email' => ['The email field is required.']],
......@@ -89,12 +91,12 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// emplid is too small
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'emplid' => 'small',
'email' => $user->getEmail(),
'doorcode' => $user->getDoorcode(),
'emplid' => 'small',
'email' => $user->getEmail(),
'doorcode' => $user->getDoorcode(),
],
['emplid' => ['The emplid must be at least 6 characters.']],
],
......@@ -102,12 +104,12 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// emplid is too large
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'emplid' => 'waaaaaaaaaaay to large',
'email' => $user->getEmail(),
'doorcode' => $user->getDoorcode(),
'emplid' => 'waaaaaaaaaaay to large',
'email' => $user->getEmail(),
'doorcode' => $user->getDoorcode(),
],
['emplid' => ['The emplid may not be greater than 7 characters.']],
],
......@@ -115,12 +117,12 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// expires at isnt a date
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
'doorcode' => $user->getDoorcode(),
'expires_at' => 'bad date',
'email' => $user->getEmail(),
'doorcode' => $user->getDoorcode(),
'expires_at' => 'bad date',
],
['expires_at' => ['The expires at is not a valid date.']],
],
......@@ -128,11 +130,11 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// doorcode isnt a number
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
'doorcode' => 'not a number',
'email' => $user->getEmail(),
'doorcode' => 'not a number',
],
['doorcode' => ['The doorcode must be a number.', 'The doorcode must be between 4 and 255 digits.']],
],
......@@ -140,11 +142,11 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// doorcode is too small
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
'doorcode' => '000',
'email' => $user->getEmail(),
'doorcode' => '000',
],
['doorcode' => ['The doorcode must be between 4 and 255 digits.']],
],
......@@ -152,11 +154,11 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// email invalid
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => 'not an email',
'doorcode' => $user->getDoorcode(),
'email' => 'not an email',
'doorcode' => $user->getDoorcode(),
],
['email' => ['The email must be a valid email address.']],
],
......@@ -191,22 +193,22 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
$this->handleTest(
'69',
[
'first_name' => 'first name',
'last_name' => 'last name',
'first_name' => 'first name',
'last_name' => 'last name',
'display_name' => 'display name',
'email' => 'email@imsad.dumb',
'doorcode' => '1234',
'email' => 'email@imsad.dumb',
'doorcode' => '1234',
]
);
$this->response->assertStatus(200);
$this->response->assertJsonFragment(
[
'first_name' => 'first name',
'last_name' => 'last name',
'first_name' => 'first name',
'last_name' => 'last name',
'display_name' => 'display name',
'emplid' => null,
'email' => 'email@imsad.dumb',
'emplid' => null,
'email' => 'email@imsad.dumb',
]
);
$this->assertEquals($this->usersRepository->get('69')->getFirstName(), 'first name');
......@@ -216,20 +218,43 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
* @test
* @throws EntityNotFoundException
*/
public function it_cannot_update_non_existient_user(): void
public function it_cannot_update_non_existent_user(): void
{
$this->authenticate();
$this->handleTest(
'asdf',
[
'first_name' => 'first name',
'last_name' => 'last name',
'display_name' => 'display name',
'email' => 'email@imsad.dumb',
'doorcode' => '1234',
]
);
$this->handleTest('asdf', [
'first_name' => 'first name',
'last_name' => 'last name',
'display_name' => 'display name',
'email' => 'email@imsad.dumb',
'doorcode' => '1234',
]);
$this->response->assertStatus(404);
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_presents_error(): void
{
$users = new InMemoryUsersRepositoryStub();
$users->setUserToReturnOnGet(new User(4, '', '', '', '', '', '', ''));
$this->app->bind(UsersRepository::class, static function () use (&$users) {
return $users;
});
$this->authenticate();
$this->handleTest('asdf', [
'first_name' => 'first name',
'last_name' => 'last name',
'display_name' => 'display name',
'email' => 'email@imsad.dumb',