diff --git a/packages/backend/src/controllers/api/v1/admin/users/update-user.ee.js b/packages/backend/src/controllers/api/v1/admin/users/update-user.ee.js new file mode 100644 index 00000000..efd1978f --- /dev/null +++ b/packages/backend/src/controllers/api/v1/admin/users/update-user.ee.js @@ -0,0 +1,18 @@ +import { renderObject } from '../../../../../helpers/renderer.js'; +import User from '../../../../../models/user.js'; + +export default async (request, response) => { + const user = await User.query() + .withGraphFetched({ + role: true, + }) + .patchAndFetchById(request.params.userId, userParams(request)) + .throwIfNotFound(); + + renderObject(response, user); +}; + +const userParams = (request) => { + const { email, fullName, roleId } = request.body; + return { email, fullName, roleId }; +}; diff --git a/packages/backend/src/controllers/api/v1/admin/users/update-user.ee.test.js b/packages/backend/src/controllers/api/v1/admin/users/update-user.ee.test.js new file mode 100644 index 00000000..04bd7f3c --- /dev/null +++ b/packages/backend/src/controllers/api/v1/admin/users/update-user.ee.test.js @@ -0,0 +1,87 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; +import Crypto from 'crypto'; +import app from '../../../../../app.js'; +import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; +import { createUser } from '../../../../../../test/factories/user.js'; +import { createRole } from '../../../../../../test/factories/role.js'; +import updateUserMock from '../../../../../../test/mocks/rest/api/v1/admin/users/update-user.js'; + +describe('PATCH /api/v1/admin/users/:userId', () => { + let currentUser, adminRole, token; + + beforeEach(async () => { + adminRole = await createRole({ key: 'admin' }); + currentUser = await createUser({ roleId: adminRole.id }); + + token = await createAuthTokenByUserId(currentUser.id); + }); + + it('should return updated user with valid data for another user', async () => { + const anotherUser = await createUser(); + const anotherRole = await createRole(); + + const anotherUserUpdatedData = { + email: 'updated@sample.com', + fullName: 'Updated Full Name', + roleId: anotherRole.id, + }; + + const response = await request(app) + .patch(`/api/v1/admin/users/${anotherUser.id}`) + .set('Authorization', token) + .send(anotherUserUpdatedData) + .expect(200); + + const refetchedAnotherUser = await anotherUser.$query(); + + const expectedPayload = updateUserMock( + { + ...refetchedAnotherUser, + ...anotherUserUpdatedData, + }, + anotherRole + ); + + expect(response.body).toMatchObject(expectedPayload); + }); + + it('should return HTTP 422 with invalid user data', async () => { + const anotherUser = await createUser(); + + const anotherUserUpdatedData = { + email: null, + fullName: null, + roleId: null, + }; + + const response = await request(app) + .patch(`/api/v1/admin/users/${anotherUser.id}`) + .set('Authorization', token) + .send(anotherUserUpdatedData) + .expect(422); + + expect(response.body.meta.type).toEqual('ModelValidation'); + expect(response.body.errors).toMatchObject({ + email: ['must be string'], + fullName: ['must be string'], + roleId: ['must be string'], + }); + }); + + it('should return not found response for not existing user UUID', async () => { + const notExistingUserUUID = Crypto.randomUUID(); + + await request(app) + .patch(`/api/v1/admin/users/${notExistingUserUUID}`) + .set('Authorization', token) + .expect(404); + }); + + it('should return bad request response for invalid UUID', async () => { + await request(app) + .get('/api/v1/admin/users/invalidUserUUID') + .set('Authorization', token) + .expect(400); + }); +}); diff --git a/packages/backend/src/routes/api/v1/admin/users.ee.js b/packages/backend/src/routes/api/v1/admin/users.ee.js index 685003e8..357e0bd9 100644 --- a/packages/backend/src/routes/api/v1/admin/users.ee.js +++ b/packages/backend/src/routes/api/v1/admin/users.ee.js @@ -3,12 +3,14 @@ import { authenticateUser } from '../../../../helpers/authentication.js'; import { authorizeAdmin } from '../../../../helpers/authorization.js'; import getUsersAction from '../../../../controllers/api/v1/admin/users/get-users.ee.js'; import getUserAction from '../../../../controllers/api/v1/admin/users/get-user.ee.js'; +import updateUserAction from '../../../../controllers/api/v1/admin/users/update-user.ee.js'; import deleteUserAction from '../../../../controllers/api/v1/admin/users/delete-user.js'; const router = Router(); router.get('/', authenticateUser, authorizeAdmin, getUsersAction); router.get('/:userId', authenticateUser, authorizeAdmin, getUserAction); +router.patch('/:userId', authenticateUser, authorizeAdmin, updateUserAction); router.delete('/:userId', authenticateUser, authorizeAdmin, deleteUserAction); export default router; diff --git a/packages/backend/test/mocks/rest/api/v1/admin/users/update-user.js b/packages/backend/test/mocks/rest/api/v1/admin/users/update-user.js new file mode 100644 index 00000000..d253d60d --- /dev/null +++ b/packages/backend/test/mocks/rest/api/v1/admin/users/update-user.js @@ -0,0 +1,29 @@ +const updateCurrentUserMock = (user, role) => { + return { + data: { + createdAt: user.createdAt.getTime(), + email: user.email, + fullName: user.fullName, + id: user.id, + status: user.status, + updatedAt: user.updatedAt.getTime(), + role: { + id: role.id, + key: role.key, + name: role.name, + isAdmin: role.isAdmin, + createdAt: role.createdAt.getTime(), + updatedAt: role.updatedAt.getTime(), + }, + }, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'User', + }, + }; +}; + +export default updateCurrentUserMock;