Commit 38861f61 authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Allow users to update themselves

Also changed the user update enpdoints to be patch instead of put cause
as it turns out thats easier for everybody
parent 5b87a686
Pipeline #2948 passed with stages
in 2 minutes and 10 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