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

Merge branch '35-allow-users-to-update-themselves' into 'master'

Resolve "Allow users to update themselves."

Closes #35

See merge request kretschmar/doorcode!28
parents 5b87a686 38861f61
Pipeline #3571 passed with stages
in 2 minutes and 2 seconds
......@@ -9,8 +9,10 @@ use Illuminate\Contracts\Auth\Guard;
use Source\Authorization\Authorizer;
use Source\Exceptions\AuthenticationException;
use Source\UseCases\Users\GetUser\GetUserUseCase;
use Source\UseCases\Users\UpdateUser\UpdateUserUseCase;
use Source\UseCases\GroupUser\GetUserGroups\GetUserGroupsUseCase;
use Source\UseCases\Users\GetUser\APIPresenter as GetUserAPIPresenter;
use Source\UseCases\Users\UpdateUser\APIPresenter as UpdateUserAPIPresenter;
use Source\UseCases\GroupUser\GetUserGroups\APIPresenter as GetUserGroupsAPIPresenter;
class MeController extends ApiController
......@@ -69,4 +71,31 @@ class MeController extends ApiController
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param \Source\UseCases\Users\UpdateUser\UpdateUserUseCase $useCase
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function update(UpdateUserUseCase $useCase): JsonResponse
{
$this->validate($this->request, [
'display_name' => 'string|max:255',
'password' => 'string|min:20|max:255',
'doorcode' => 'string|numeric|digits_between:11,255',
]);
$attributes = $this->request->all();
$attributes['salt'] = config('app.key');
$presenter = new UpdateUserAPIPresenter();
$useCase->update($this->userId, $attributes, $presenter);
if ($presenter->hasError()) {
return $this->respondWithError($presenter->getViewModel()['message']);
}
return $this->respondWithData($presenter->getViewModel());
}
}
......@@ -107,11 +107,11 @@ class UsersController extends ApiController
$this->authorizer->protectAll([Permissions::MANAGE_USERS]);
$this->validate($this->request, [
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'display_name' => 'required|string|max:255',
'first_name' => 'string|max:255',
'last_name' => 'string|max:255',
'display_name' => 'string|max:255',
'emplid' => 'nullable|string|max:7|min:6',
'email' => 'required|email|max:255',
'email' => 'email|max:255',
'password' => 'nullable|string|max:255',
'doorcode' => 'nullable|string|numeric|digits_between:4,255',
'expires_at' => 'nullable|string|date|max:255',
......
......@@ -27,7 +27,7 @@ Route::group(['middleware' => 'auth:api'], static function () {
Route::get('/', [UsersController::class, 'index']);
Route::post('/', [UsersController::class, 'store']);
Route::get('{userId}', [UsersController::class, 'get']);
Route::put('{userId}', [UsersController::class, 'update']);
Route::patch('{userId}', [UsersController::class, 'update']);
Route::delete('{userId}', [UsersController::class, 'delete']);
Route::get('{userId}/groups', [UsersController::class, 'getGroupsForUser']);
......@@ -53,5 +53,6 @@ Route::group(['middleware' => 'auth:api'], static function () {
], static function () {
Route::get('/', [MeController::class, 'index']);
Route::get('/groups', [MeController::class, 'groups']);
Route::patch('/', [MeController::class, 'update']);
});
});
......@@ -54,11 +54,11 @@ class UpdateUser implements UpdateUserUseCase
// updatedAt will get overwritten
$newUser = new User(
$user->getId(),
$attributes['first_name'],
$attributes['last_name'],
$attributes['display_name'],
$attributes['email'],
$attributes['emplid'] ?? null,
$attributes['first_name'] ?? $user->getFirstName(),
$attributes['last_name'] ?? $user->getLastName(),
$attributes['display_name'] ?? $user->getDisplayName(),
$attributes['email'] ?? $user->getEmail(),
$attributes['emplid'] ?? $user->getEmplid(),
Password::hash($attributes['password'] ?? null) ?? $user->getPassword(),
Doorcode::hash($attributes['salt'], $attributes['doorcode'] ?? null) ?? $user->getDoorcode(),
$expires,
......
......@@ -9,12 +9,12 @@ interface UpdateUserUseCase
{
/**
* Required attributes:
* salt (the salt to hash the doorcode with)
* Optional attributes
* first_name
* last_name
* display_name
* email
* salt (the salt to hash the doorcode with)
* Optional attributes
* emplid
* expires_at
* password
......
......@@ -13,10 +13,19 @@ use Source\Gateways\GroupUser\InMemoryGroupUserRepository;
class GetCurrentUserGroupsApiTest extends AuthenticatesWithApplicationTestCase
{
/**
* @var \Illuminate\Foundation\Testing\TestResponse
*/
protected TestResponse $response;
/**
* @var mixed|\Source\Gateways\GroupUser\InMemoryGroupUserRepository
*/
protected InMemoryGroupUserRepository $repository;
/**
* @var mixed|\Source\Gateways\Groups\InMemoryGroupsRepository
*/
protected InMemoryGroupsRepository $groups;
public function setUp(): void
......
<?php
namespace Tests\Feature\Api\Me;
use Source\Entities\User;
use Source\Entities\Doorcode;
use Source\Gateways\Users\UsersRepository;
use Source\Gateways\Groups\GroupsRepository;
use Source\Exceptions\EntityNotFoundException;
use Tests\Doubles\InMemoryUsersRepositoryStub;
use Illuminate\Foundation\Testing\TestResponse;
use Source\Gateways\GroupUser\GroupUserRepository;
use Source\Gateways\Groups\InMemoryGroupsRepository;
use Tests\Feature\AuthenticatesWithApplicationTestCase;
use Source\Gateways\GroupUser\InMemoryGroupUserRepository;
class UpdateCurrentUserApiTest extends AuthenticatesWithApplicationTestCase
{
/**
* @var \Illuminate\Foundation\Testing\TestResponse
*/
protected TestResponse $response;
/**
* @var mixed|\Source\Gateways\GroupUser\InMemoryGroupUserRepository
*/
protected InMemoryGroupUserRepository $repository;
/**
* @var mixed|\Source\Gateways\Groups\InMemoryGroupsRepository
*/
protected InMemoryGroupsRepository $groups;
public function setUp(): void
{
parent::setUp();
$this->groups = $this->app->make(GroupsRepository::class);
$this->repository = $this->app->make(GroupUserRepository::class);
}
protected function handleTest(array $attributes): void
{
$this->response = $this->patchJson(
'/me',
array_merge(['api_token' => $this->authToken], $attributes)
);
}
/**
* @test
*/
public function it_denies_unauthorized(): void
{
$this->handleTest([]);
$this->response->assertStatus(401);
}
public function invalidAttributesProvider(): array
{
return [
// Test for password too small
[
[
'password' => 'lol to short',
],
['password' => ['The password must be at least 20 characters.']],
],
// Test for doorcode too short
[
[
'doorcode' => '1234567890',
],
['doorcode' => ['The doorcode must be between 11 and 255 digits.']],
],
];
}
/**
* @test
* @param array $data
* @param array $message
* @dataProvider invalidAttributesProvider
* @throws EntityNotFoundException
*/
public function it_tests_the_validation_rules(array $data, array $message): void
{
$this->authenticate();
$this->handleTest($data);
$this->response->assertJsonFragment($message);
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_changes_the_display_name(): void
{
$this->authenticate();
$this->handleTest(['display_name' => 'his majesty the third']);
$this->assertEquals('his majesty the third', $this->usersRepository->get(1)->getDisplayName());
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_changes_the_password(): void
{
$this->authenticate();
$this->handleTest(['password' => 'his majesty the third']);
$this->assertTrue($this->usersRepository->get(1)->getPassword()->matches('his majesty the third'));
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_changes_the_doorcode(): void
{
$this->authenticate();
$this->handleTest(['doorcode' => '12345678909']);
$this->assertEquals(Doorcode::hash(config('app.key'), '12345678909')->getHash(), $this->usersRepository->get(1)->getDoorcode()->getHash());
}
/**
* @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([]);
$this->response->assertStatus(200);
$this->response->assertJson(['status' => 'error', 'message' => 'Unable to update user.']);
}
}
......@@ -21,7 +21,7 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
*/
protected function handleTest(string $userId, array $data): void
{
$this->response = $this->putJson(
$this->response = $this->patchJson(
'/users/' . $userId,
array_merge(['api_token' => $this->authToken], $data)
);
......@@ -52,61 +52,11 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
public function invalidUserProvider(): array
{
$user = new User(
0,
'first',
'last',
'display',
't@t.com',
null,
null,
new Doorcode('43252643')
);
return [
// Missing one of the required
[
[
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
],
['first_name' => ['The first name field is required.']],
],
[
[
'first_name' => $user->getFirstName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
],
['last_name' => ['The last name field is required.']],
],
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'email' => $user->getEmail(),
],
['display_name' => ['The display name field is required.']],
],
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
],
['email' => ['The email field is required.']],
],
// emplid is too small
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'emplid' => 'small',
'email' => $user->getEmail(),
'doorcode' => $user->getDoorcode()->getHash(),
],
['emplid' => ['The emplid must be at least 6 characters.']],
],
......@@ -114,12 +64,7 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// emplid is too large
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'emplid' => 'waaaaaaaaaaay to large',
'email' => $user->getEmail(),
'doorcode' => $user->getDoorcode()->getHash(),
],
['emplid' => ['The emplid may not be greater than 7 characters.']],
],
......@@ -127,11 +72,6 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// expires at isnt a date
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
'doorcode' => $user->getDoorcode()->getHash(),
'expires_at' => 'bad date',
],
['expires_at' => ['The expires at is not a valid date.']],
......@@ -140,10 +80,6 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// doorcode isnt a number
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
'doorcode' => 'not a number',
],
['doorcode' => ['The doorcode must be a number.', 'The doorcode must be between 4 and 255 digits.']],
......@@ -152,10 +88,6 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// doorcode is too small
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => $user->getEmail(),
'doorcode' => '000',
],
['doorcode' => ['The doorcode must be between 4 and 255 digits.']],
......@@ -164,11 +96,7 @@ class UpdateUserApiTest extends AuthenticatesWithApplicationTestCase
// email invalid
[
[
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
'display_name' => $user->getDisplayName(),
'email' => 'not an email',
'doorcode' => $user->getDoorcode()->getHash(),
],
['email' => ['The email must be a valid email address.']],
],
......
......@@ -183,4 +183,30 @@ class UseCaseTest extends TestCase
$this->assertEquals('2020-03-07T20:58:50+00:00', $this->response->getUser()->getExpiresAt()->format('c'));
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_will_update_nothing_if_no_changes_are_required(): void
{
$user = new User(
69,
'scat',
'man',
'IM THE SCAT MAN',
'do biddle be bahhhh',
'hes the scat man',
new Password('hash'),
new Doorcode('hash')
);
$this->usersRepository->create($user);
$this->handleTest('69', ['salt' => '']);
$this->assertFalse($this->response->hasError());
$this->assertEquals($user, $this->response->getUser());
$this->assertTrue($this->authorizer->adminRightsWereProtected());
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment