From e9f39e4db8cfc7f7783ddfa50bdf1eb6360047d0 Mon Sep 17 00:00:00 2001 From: Eric Lim Date: Mon, 4 Nov 2024 15:51:35 +0100 Subject: [PATCH 1/3] refactor(api): move /api/admin/users/{id} route and tests in src/identity-access-management --- api/lib/application/users/index.js | 32 ---- .../application/user/user.admin.route.js | 33 ++++ ...troller-get-user-details-for-admin_test.js | 153 ------------------ .../application/user/user.admin.route.test.js | 146 ++++++++++++++++- .../application/user/user.admin.route.test.js | 56 +++++++ .../application/users/index_test.js | 40 ----- .../unit/application/users/index_test.js | 37 ----- 7 files changed, 234 insertions(+), 263 deletions(-) delete mode 100644 api/tests/acceptance/application/users/users-controller-get-user-details-for-admin_test.js diff --git a/api/lib/application/users/index.js b/api/lib/application/users/index.js index 43f7137cdc1..aeb54557765 100644 --- a/api/lib/application/users/index.js +++ b/api/lib/application/users/index.js @@ -1,43 +1,11 @@ import Joi from 'joi'; -import { BadRequestError, sendJsonApiError } from '../../../src/shared/application/http-errors.js'; import { securityPreHandlers } from '../../../src/shared/application/security-pre-handlers.js'; import { identifiersType } from '../../../src/shared/domain/types/identifiers-type.js'; import { userController } from './user-controller.js'; const register = async function (server) { const adminRoutes = [ - { - method: 'GET', - path: '/api/admin/users/{id}', - config: { - validate: { - params: Joi.object({ - id: identifiersType.userId, - }), - failAction: (request, h) => { - return sendJsonApiError(new BadRequestError("L'identifiant de l'utilisateur n'est pas au bon format."), h); - }, - }, - pre: [ - { - method: (request, h) => - securityPreHandlers.hasAtLeastOneAccessOf([ - securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, - securityPreHandlers.checkAdminMemberHasRoleCertif, - securityPreHandlers.checkAdminMemberHasRoleSupport, - securityPreHandlers.checkAdminMemberHasRoleMetier, - ])(request, h), - }, - ], - handler: userController.getUserDetailsForAdmin, - notes: [ - '- **Cette route est restreinte aux utilisateurs administrateurs**\n' + - "- Elle permet de récupérer le détail d'un utilisateur dans un contexte d'administration", - ], - tags: ['api', 'admin', 'user'], - }, - }, { method: 'GET', path: '/api/admin/users/{id}/organizations', diff --git a/api/src/identity-access-management/application/user/user.admin.route.js b/api/src/identity-access-management/application/user/user.admin.route.js index a9610a21525..ac11e639250 100644 --- a/api/src/identity-access-management/application/user/user.admin.route.js +++ b/api/src/identity-access-management/application/user/user.admin.route.js @@ -1,5 +1,7 @@ import Joi from 'joi'; +import { userController } from '../../../../lib/application/users/user-controller.js'; +import { BadRequestError, sendJsonApiError } from '../../../shared/application/http-errors.js'; import { securityPreHandlers } from '../../../shared/application/security-pre-handlers.js'; import { SUPPORTED_LOCALES } from '../../../shared/domain/constants.js'; import { AVAILABLE_LANGUAGES } from '../../../shared/domain/services/language-service.js'; @@ -120,4 +122,35 @@ export const userAdminRoutes = [ tags: ['api', 'admin', 'identity-access-management'], }, }, + { + method: 'GET', + path: '/api/admin/users/{id}', + config: { + validate: { + params: Joi.object({ + id: identifiersType.userId, + }), + failAction: (request, h) => { + return sendJsonApiError(new BadRequestError("L'identifiant de l'utilisateur n'est pas au bon format."), h); + }, + }, + pre: [ + { + method: (request, h) => + securityPreHandlers.hasAtLeastOneAccessOf([ + securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, + securityPreHandlers.checkAdminMemberHasRoleCertif, + securityPreHandlers.checkAdminMemberHasRoleSupport, + securityPreHandlers.checkAdminMemberHasRoleMetier, + ])(request, h), + }, + ], + handler: (request, h) => userController.getUserDetailsForAdmin(request, h), + notes: [ + '- **Cette route est restreinte aux utilisateurs administrateurs**\n' + + "- Elle permet de récupérer le détail d'un utilisateur dans un contexte d'administration", + ], + tags: ['api', 'admin', 'identity-access-management', 'user'], + }, + }, ]; diff --git a/api/tests/acceptance/application/users/users-controller-get-user-details-for-admin_test.js b/api/tests/acceptance/application/users/users-controller-get-user-details-for-admin_test.js deleted file mode 100644 index 8533c8cf0a8..00000000000 --- a/api/tests/acceptance/application/users/users-controller-get-user-details-for-admin_test.js +++ /dev/null @@ -1,153 +0,0 @@ -import { - createServer, - databaseBuilder, - expect, - generateValidRequestAuthorizationHeader, - insertUserWithRoleSuperAdmin, - sinon, -} from '../../../test-helper.js'; - -describe('Acceptance | Controller | users-controller-get-user-details-for-admin', function () { - let clock; - let server; - - beforeEach(async function () { - clock = sinon.useFakeTimers({ - now: Date.now(), - toFake: ['Date'], - }); - - server = await createServer(); - }); - - afterEach(function () { - clock.restore(); - }); - - describe('GET /admin/users/:id', function () { - describe('Resource access management', function () { - it('should respond with a 403 - forbidden access - if requested user is not the same as authenticated user', async function () { - // given - const user = databaseBuilder.factory.buildUser(); - await databaseBuilder.commit(); - - const otherUserId = 9999; - - // when - const response = await server.inject({ - method: 'GET', - url: `/api/admin/users/${user.id}`, - payload: {}, - headers: { - authorization: generateValidRequestAuthorizationHeader(otherUserId), - }, - }); - - // then - expect(response.statusCode).to.equal(403); - }); - }); - - describe('Success case', function () { - it('should return 200 and user serialized', async function () { - // given - const superAdmin = await insertUserWithRoleSuperAdmin(); - - const user = databaseBuilder.factory.buildUser({ username: 'brice.glace0712', locale: 'fr-FR' }); - const blockedAt = new Date('2022-12-07'); - const temporaryBlockedUntil = new Date('2022-12-06'); - const userLoginId = databaseBuilder.factory.buildUserLogin({ - failureCount: 666, - blockedAt, - temporaryBlockedUntil, - userId: user.id, - lastLoggedAt: new Date(), - }).id; - - await databaseBuilder.commit(); - - // when - const response = await server.inject({ - method: 'GET', - url: `/api/admin/users/${user.id}`, - payload: {}, - headers: { - authorization: generateValidRequestAuthorizationHeader(superAdmin.id), - }, - }); - - // then - expect(response.statusCode).to.equal(200); - expect(response.result.data.id).to.deep.equal(`${user.id}`); - expect(response.result.data.type).to.deep.equal('users'); - - expect(response.result.data.attributes).to.deep.equal({ - cgu: true, - 'created-at': new Date(), - email: user.email, - 'email-confirmed-at': null, - 'first-name': user.firstName, - lang: 'fr', - locale: 'fr-FR', - 'last-logged-at': new Date(), - 'last-name': user.lastName, - 'last-pix-certif-terms-of-service-validated-at': null, - 'last-pix-orga-terms-of-service-validated-at': null, - 'last-terms-of-service-validated-at': null, - 'pix-certif-terms-of-service-accepted': false, - 'pix-orga-terms-of-service-accepted': false, - username: user.username, - 'has-been-anonymised': false, - 'anonymised-by-full-name': null, - 'is-pix-agent': false, - }); - - expect(response.result.data.relationships).to.deep.equal({ - 'authentication-methods': { - data: [], - }, - 'certification-center-memberships': { - links: { - related: `/api/admin/users/${user.id}/certification-center-memberships`, - }, - }, - 'organization-learners': { - data: [], - }, - profile: { - links: { - related: `/api/admin/users/${user.id}/profile`, - }, - }, - 'organization-memberships': { - links: { - related: `/api/admin/users/${user.id}/organizations`, - }, - }, - 'user-login': { - data: { - id: `${userLoginId}`, - type: 'userLogins', - }, - }, - participations: { - links: { - related: `/api/admin/users/${user.id}/participations`, - }, - }, - }); - expect(response.result.included).to.deep.equal([ - { - id: `${userLoginId}`, - type: 'userLogins', - attributes: { - 'failure-count': 666, - 'blocked-at': blockedAt, - 'temporary-blocked-until': temporaryBlockedUntil, - }, - }, - ]); - }); - }); - }); -}); diff --git a/api/tests/identity-access-management/acceptance/application/user/user.admin.route.test.js b/api/tests/identity-access-management/acceptance/application/user/user.admin.route.test.js index 3ca3aa1f5ce..74963b19542 100644 --- a/api/tests/identity-access-management/acceptance/application/user/user.admin.route.test.js +++ b/api/tests/identity-access-management/acceptance/application/user/user.admin.route.test.js @@ -4,6 +4,7 @@ import { expect, generateValidRequestAuthorizationHeader, insertUserWithRoleSuperAdmin, + sinon, } from '../../../../test-helper.js'; describe('Acceptance | Identity Access Management | Application | Route | Admin | User', function () { @@ -13,7 +14,7 @@ describe('Acceptance | Identity Access Management | Application | Route | Admin server = await createServer(); }); - describe('PUT /admin/users/:id/unblock', function () { + describe('PUT /admin/users/{id}/unblock', function () { it('unblocks user how has tried to many wrong password', async function () { // given const userId = databaseBuilder.factory.buildUser.withRawPassword().id; @@ -166,4 +167,147 @@ describe('Acceptance | Identity Access Management | Application | Route | Admin }); }); }); + + describe('GET /admin/users/{id}', function () { + let clock; + let server; + + beforeEach(async function () { + clock = sinon.useFakeTimers({ + now: Date.now(), + toFake: ['Date'], + }); + + server = await createServer(); + }); + + afterEach(function () { + clock.restore(); + }); + + describe('Resource access management', function () { + it('responds with a 403 - forbidden access - if requested user is not the same as authenticated user', async function () { + // given + const user = databaseBuilder.factory.buildUser(); + await databaseBuilder.commit(); + + const otherUserId = 9999; + + // when + const response = await server.inject({ + method: 'GET', + url: `/api/admin/users/${user.id}`, + payload: {}, + headers: { + authorization: generateValidRequestAuthorizationHeader(otherUserId), + }, + }); + + // then + expect(response.statusCode).to.equal(403); + }); + }); + + describe('Success case', function () { + it('returns 200 and user serialized', async function () { + // given + const superAdmin = await insertUserWithRoleSuperAdmin(); + + const user = databaseBuilder.factory.buildUser({ username: 'brice.glace0712', locale: 'fr-FR' }); + const blockedAt = new Date('2022-12-07'); + const temporaryBlockedUntil = new Date('2022-12-06'); + const userLoginId = databaseBuilder.factory.buildUserLogin({ + failureCount: 666, + blockedAt, + temporaryBlockedUntil, + userId: user.id, + lastLoggedAt: new Date(), + }).id; + + await databaseBuilder.commit(); + + // when + const response = await server.inject({ + method: 'GET', + url: `/api/admin/users/${user.id}`, + payload: {}, + headers: { + authorization: generateValidRequestAuthorizationHeader(superAdmin.id), + }, + }); + + // then + expect(response.statusCode).to.equal(200); + expect(response.result.data.id).to.deep.equal(`${user.id}`); + expect(response.result.data.type).to.deep.equal('users'); + + expect(response.result.data.attributes).to.deep.equal({ + cgu: true, + 'created-at': new Date(), + email: user.email, + 'email-confirmed-at': null, + 'first-name': user.firstName, + lang: 'fr', + locale: 'fr-FR', + 'last-logged-at': new Date(), + 'last-name': user.lastName, + 'last-pix-certif-terms-of-service-validated-at': null, + 'last-pix-orga-terms-of-service-validated-at': null, + 'last-terms-of-service-validated-at': null, + 'pix-certif-terms-of-service-accepted': false, + 'pix-orga-terms-of-service-accepted': false, + username: user.username, + 'has-been-anonymised': false, + 'anonymised-by-full-name': null, + 'is-pix-agent': false, + }); + + expect(response.result.data.relationships).to.deep.equal({ + 'authentication-methods': { + data: [], + }, + 'certification-center-memberships': { + links: { + related: `/api/admin/users/${user.id}/certification-center-memberships`, + }, + }, + 'organization-learners': { + data: [], + }, + profile: { + links: { + related: `/api/admin/users/${user.id}/profile`, + }, + }, + 'organization-memberships': { + links: { + related: `/api/admin/users/${user.id}/organizations`, + }, + }, + 'user-login': { + data: { + id: `${userLoginId}`, + type: 'userLogins', + }, + }, + participations: { + links: { + related: `/api/admin/users/${user.id}/participations`, + }, + }, + }); + expect(response.result.included).to.deep.equal([ + { + id: `${userLoginId}`, + type: 'userLogins', + attributes: { + 'failure-count': 666, + 'blocked-at': blockedAt, + 'temporary-blocked-until': temporaryBlockedUntil, + }, + }, + ]); + }); + }); + }); }); diff --git a/api/tests/identity-access-management/integration/application/user/user.admin.route.test.js b/api/tests/identity-access-management/integration/application/user/user.admin.route.test.js index c02b4f17cf4..e5434f4859a 100644 --- a/api/tests/identity-access-management/integration/application/user/user.admin.route.test.js +++ b/api/tests/identity-access-management/integration/application/user/user.admin.route.test.js @@ -1,3 +1,4 @@ +import { userController } from '../../../../../lib/application/users/user-controller.js'; import { identityAccessManagementRoutes } from '../../../../../src/identity-access-management/application/routes.js'; import { userAdminController } from '../../../../../src/identity-access-management/application/user/user.admin.controller.js'; import { securityPreHandlers } from '../../../../../src/shared/application/security-pre-handlers.js'; @@ -141,4 +142,59 @@ describe('Integration | Identity Access Management | Application | Route | Admin expect(response.statusCode).to.equal(400); }); }); + + describe('GET /api/admin/users/{id}', function () { + it('returns an HTTP status code 200', async function () { + // given + sinon.stub(securityPreHandlers, 'hasAtLeastOneAccessOf').returns(() => true); + sinon.stub(userController, 'getUserDetailsForAdmin').resolves('ok'); + + // when + const response = await httpTestServer.request('GET', '/api/admin/users/8'); + + // then + expect(response.statusCode).to.equal(200); + sinon.assert.calledOnce(securityPreHandlers.hasAtLeastOneAccessOf); + sinon.assert.calledOnce(userController.getUserDetailsForAdmin); + }); + + it('returns an HTTP status code 403', async function () { + // given + sinon.stub(securityPreHandlers, 'hasAtLeastOneAccessOf').returns((request, h) => + h + .response({ errors: new Error('') }) + .code(403) + .takeover(), + ); + + // when + const response = await httpTestServer.request('GET', '/api/admin/users/8'); + + // then + expect(response.statusCode).to.equal(403); + sinon.assert.calledOnce(securityPreHandlers.hasAtLeastOneAccessOf); + }); + + it('returns BAD_REQUEST (400) when id in param is not a number"', async function () { + // given + const url = '/api/admin/users/NOT_A_NUMBER'; + + // when + const response = await httpTestServer.request('GET', url); + + // then + expect(response.statusCode).to.equal(400); + }); + + it('returns BAD_REQUEST (400) when id in params is out of range"', async function () { + // given + const url = '/api/admin/users/0'; + + // when + const response = await httpTestServer.request('GET', url); + + // then + expect(response.statusCode).to.equal(400); + }); + }); }); diff --git a/api/tests/integration/application/users/index_test.js b/api/tests/integration/application/users/index_test.js index bb0f59cee5c..598ee29fbd5 100644 --- a/api/tests/integration/application/users/index_test.js +++ b/api/tests/integration/application/users/index_test.js @@ -4,7 +4,6 @@ import { securityPreHandlers } from '../../../../src/shared/application/security import { expect, HttpTestServer, sinon } from '../../../test-helper.js'; describe('Integration | Application | Users | Routes', function () { - const methodGET = 'GET'; const methodPATCH = 'PATCH'; let httpTestServer; @@ -15,7 +14,6 @@ describe('Integration | Application | Users | Routes', function () { .stub(securityPreHandlers, 'checkRequestedUserIsAuthenticatedUser') .callsFake((request, h) => h.response(true)); - sinon.stub(userController, 'getUserDetailsForAdmin').returns('ok'); sinon.stub(userController, 'resetScorecard').returns('ok'); sinon.stub(userController, 'rememberUserHasSeenChallengeTooltip').returns('ok'); @@ -73,42 +71,4 @@ describe('Integration | Application | Users | Routes', function () { expect(response.statusCode).to.equal(200); }); }); - - context('Routes /admin', function () { - describe('GET /api/admin/users/{id}', function () { - it('should exist', async function () { - // given - securityPreHandlers.hasAtLeastOneAccessOf.returns(() => true); - const url = '/api/admin/users/123'; - - // when - const response = await httpTestServer.request(methodGET, url); - - // then - expect(response.statusCode).to.equal(200); - }); - - it('should return BAD_REQUEST (400) when id in param is not a number"', async function () { - // given - const url = '/api/admin/users/NOT_A_NUMBER'; - - // when - const response = await httpTestServer.request(methodGET, url); - - // then - expect(response.statusCode).to.equal(400); - }); - - it('should return BAD_REQUEST (400) when id in param is out of range"', async function () { - // given - const url = '/api/admin/users/0'; - - // when - const response = await httpTestServer.request(methodGET, url); - - // then - expect(response.statusCode).to.equal(400); - }); - }); - }); }); diff --git a/api/tests/unit/application/users/index_test.js b/api/tests/unit/application/users/index_test.js index ac4f1cf2994..a11f60f8594 100644 --- a/api/tests/unit/application/users/index_test.js +++ b/api/tests/unit/application/users/index_test.js @@ -101,43 +101,6 @@ describe('Unit | Router | user-router', function () { }); context('Routes /admin', function () { - describe('GET /api/admin/users/{id}', function () { - it('returns an HTTP status code 200', async function () { - // given - sinon.stub(securityPreHandlers, 'hasAtLeastOneAccessOf').returns(() => true); - sinon.stub(userController, 'getUserDetailsForAdmin').resolves('ok'); - const httpTestServer = new HttpTestServer(); - await httpTestServer.register(moduleUnderTest); - - // when - const response = await httpTestServer.request('GET', '/api/admin/users/8'); - - // then - expect(response.statusCode).to.equal(200); - sinon.assert.calledOnce(securityPreHandlers.hasAtLeastOneAccessOf); - sinon.assert.calledOnce(userController.getUserDetailsForAdmin); - }); - - it('returns an HTTP status code 403', async function () { - // given - sinon.stub(securityPreHandlers, 'hasAtLeastOneAccessOf').returns((request, h) => - h - .response({ errors: new Error('') }) - .code(403) - .takeover(), - ); - const httpTestServer = new HttpTestServer(); - await httpTestServer.register(moduleUnderTest); - - // when - const response = await httpTestServer.request('GET', '/api/admin/users/8'); - - // then - expect(response.statusCode).to.equal(403); - sinon.assert.calledOnce(securityPreHandlers.hasAtLeastOneAccessOf); - }); - }); - describe('POST /api/admin/users/{id}/anonymize', function () { it('returns 200 when user role is "SUPER_ADMIN"', async function () { // given From 17350f820d3405ba0f92df91d1aa54e9ff9995e1 Mon Sep 17 00:00:00 2001 From: Eric Lim Date: Mon, 4 Nov 2024 16:11:14 +0100 Subject: [PATCH 2/3] refactor(api): move and rename get-user-details-for-admin controller --- api/lib/application/users/user-controller.js | 10 ++----- .../application/user/user.admin.controller.js | 23 +++++++++++++- .../application/user/user.admin.route.js | 3 +- .../application/user/user.admin.route.test.js | 5 ++-- .../user/user.admin.controller.test.js | 26 ++++++++++++++++ .../application/users/user-controller_test.js | 30 ++----------------- 6 files changed, 56 insertions(+), 41 deletions(-) diff --git a/api/lib/application/users/user-controller.js b/api/lib/application/users/user-controller.js index ab9639f04c6..d58557653a6 100644 --- a/api/lib/application/users/user-controller.js +++ b/api/lib/application/users/user-controller.js @@ -2,6 +2,7 @@ import { usecases as devcompUsecases } from '../../../src/devcomp/domain/usecase import * as trainingSerializer from '../../../src/devcomp/infrastructure/serializers/jsonapi/training-serializer.js'; import { evaluationUsecases } from '../../../src/evaluation/domain/usecases/index.js'; import * as scorecardSerializer from '../../../src/evaluation/infrastructure/serializers/jsonapi/scorecard-serializer.js'; +import { usecases as identityAccessManagementUsecases } from '../../../src/identity-access-management/domain/usecases/index.js'; import * as userDetailsForAdminSerializer from '../../../src/identity-access-management/infrastructure/serializers/jsonapi/user-details-for-admin.serializer.js'; import * as campaignParticipationSerializer from '../../../src/prescription/campaign-participation/infrastructure/serializers/jsonapi/campaign-participation-serializer.js'; import * as userSerializer from '../../../src/shared/infrastructure/serializers/jsonapi/user-serializer.js'; @@ -13,12 +14,6 @@ import * as certificationCenterMembershipSerializer from '../../infrastructure/s import * as userAnonymizedDetailsForAdminSerializer from '../../infrastructure/serializers/jsonapi/user-anonymized-details-for-admin-serializer.js'; import * as userOrganizationForAdminSerializer from '../../infrastructure/serializers/jsonapi/user-organization-for-admin-serializer.js'; -const getUserDetailsForAdmin = async function (request, h, dependencies = { userDetailsForAdminSerializer }) { - const userId = request.params.id; - const userDetailsForAdmin = await usecases.getUserDetailsForAdmin({ userId }); - return dependencies.userDetailsForAdminSerializer.serialize(userDetailsForAdmin); -}; - const rememberUserHasSeenAssessmentInstructions = async function (request, h, dependencies = { userSerializer }) { const authenticatedUserId = request.auth.credentials.userId; @@ -121,7 +116,7 @@ const anonymizeUser = async function (request, h, dependencies = { userAnonymize }); }); - const anonymizedUser = await usecases.getUserDetailsForAdmin({ userId: userToAnonymizeId }); + const anonymizedUser = await identityAccessManagementUsecases.getUserDetailsForAdmin({ userId: userToAnonymizeId }); return h.response(dependencies.userAnonymizedDetailsForAdminSerializer.serialize(anonymizedUser)).code(200); }; @@ -196,7 +191,6 @@ const userController = { getCampaignParticipationOverviews, getCampaignParticipations, getUserCampaignParticipationToCampaign, - getUserDetailsForAdmin, reassignAuthenticationMethods, rememberUserHasSeenAssessmentInstructions, rememberUserHasSeenChallengeTooltip, diff --git a/api/src/identity-access-management/application/user/user.admin.controller.js b/api/src/identity-access-management/application/user/user.admin.controller.js index adad9cae9d5..ceeb7c70b68 100644 --- a/api/src/identity-access-management/application/user/user.admin.controller.js +++ b/api/src/identity-access-management/application/user/user.admin.controller.js @@ -1,3 +1,4 @@ +import { usecases as libUsecases } from '../../../../lib/domain/usecases/index.js'; import { usecases } from '../../domain/usecases/index.js'; import * as userDetailsForAdminSerializer from '../../infrastructure/serializers/jsonapi/user-details-for-admin.serializer.js'; import * as userForAdminSerializer from '../../infrastructure/serializers/jsonapi/user-for-admin.serializer.js'; @@ -48,12 +49,32 @@ const updateUserDetailsByAdmin = async function (request, h, dependencies = { us return dependencies.userDetailsForAdminSerializer.serializeForUpdate(updatedUser); }; +/** + * + * @param request + * @param h + * @param dependencies + * @param {UserDetailsForAdminSerializer} dependencies.userDetailsForAdminSerializer + * @returns {Promise<*>} + */ +const getUserDetails = async function (request, h, dependencies = { userDetailsForAdminSerializer }) { + const userId = request.params.id; + const userDetailsForAdmin = await libUsecases.getUserDetailsForAdmin({ userId }); + return dependencies.userDetailsForAdminSerializer.serialize(userDetailsForAdmin); +}; + /** * @typedef {object} UserAdminController * @property {function} findPaginatedFilteredUsers + * @property {function} getUserDetails * @property {function} unblockUserAccount * @property {function} updateUserDetailsByAdmin */ -const userAdminController = { findPaginatedFilteredUsers, unblockUserAccount, updateUserDetailsByAdmin }; +const userAdminController = { + findPaginatedFilteredUsers, + getUserDetails, + unblockUserAccount, + updateUserDetailsByAdmin, +}; export { userAdminController }; diff --git a/api/src/identity-access-management/application/user/user.admin.route.js b/api/src/identity-access-management/application/user/user.admin.route.js index ac11e639250..663ac27cc18 100644 --- a/api/src/identity-access-management/application/user/user.admin.route.js +++ b/api/src/identity-access-management/application/user/user.admin.route.js @@ -1,6 +1,5 @@ import Joi from 'joi'; -import { userController } from '../../../../lib/application/users/user-controller.js'; import { BadRequestError, sendJsonApiError } from '../../../shared/application/http-errors.js'; import { securityPreHandlers } from '../../../shared/application/security-pre-handlers.js'; import { SUPPORTED_LOCALES } from '../../../shared/domain/constants.js'; @@ -145,7 +144,7 @@ export const userAdminRoutes = [ ])(request, h), }, ], - handler: (request, h) => userController.getUserDetailsForAdmin(request, h), + handler: (request, h) => userAdminController.getUserDetails(request, h), notes: [ '- **Cette route est restreinte aux utilisateurs administrateurs**\n' + "- Elle permet de récupérer le détail d'un utilisateur dans un contexte d'administration", diff --git a/api/tests/identity-access-management/integration/application/user/user.admin.route.test.js b/api/tests/identity-access-management/integration/application/user/user.admin.route.test.js index e5434f4859a..0ab43e7cb1f 100644 --- a/api/tests/identity-access-management/integration/application/user/user.admin.route.test.js +++ b/api/tests/identity-access-management/integration/application/user/user.admin.route.test.js @@ -1,4 +1,3 @@ -import { userController } from '../../../../../lib/application/users/user-controller.js'; import { identityAccessManagementRoutes } from '../../../../../src/identity-access-management/application/routes.js'; import { userAdminController } from '../../../../../src/identity-access-management/application/user/user.admin.controller.js'; import { securityPreHandlers } from '../../../../../src/shared/application/security-pre-handlers.js'; @@ -147,7 +146,7 @@ describe('Integration | Identity Access Management | Application | Route | Admin it('returns an HTTP status code 200', async function () { // given sinon.stub(securityPreHandlers, 'hasAtLeastOneAccessOf').returns(() => true); - sinon.stub(userController, 'getUserDetailsForAdmin').resolves('ok'); + sinon.stub(userAdminController, 'getUserDetails').resolves('ok'); // when const response = await httpTestServer.request('GET', '/api/admin/users/8'); @@ -155,7 +154,7 @@ describe('Integration | Identity Access Management | Application | Route | Admin // then expect(response.statusCode).to.equal(200); sinon.assert.calledOnce(securityPreHandlers.hasAtLeastOneAccessOf); - sinon.assert.calledOnce(userController.getUserDetailsForAdmin); + sinon.assert.calledOnce(userAdminController.getUserDetails); }); it('returns an HTTP status code 403', async function () { diff --git a/api/tests/identity-access-management/unit/application/user/user.admin.controller.test.js b/api/tests/identity-access-management/unit/application/user/user.admin.controller.test.js index a779461c8f4..1e0709bd6f2 100644 --- a/api/tests/identity-access-management/unit/application/user/user.admin.controller.test.js +++ b/api/tests/identity-access-management/unit/application/user/user.admin.controller.test.js @@ -1,3 +1,4 @@ +import { usecases as libUsecases } from '../../../../../lib/domain/usecases/index.js'; import { userAdminController } from '../../../../../src/identity-access-management/application/user/user.admin.controller.js'; import { User } from '../../../../../src/identity-access-management/domain/models/User.js'; import { usecases } from '../../../../../src/identity-access-management/domain/usecases/index.js'; @@ -159,4 +160,29 @@ describe('Unit | Identity Access Management | Application | Controller | Admin | expect(response).to.be.equal(newEmail); }); }); + + describe('#getUserDetails', function () { + let request; + let dependencies; + + beforeEach(function () { + request = { params: { id: 123 } }; + + sinon.stub(libUsecases, 'getUserDetailsForAdmin'); + const userDetailsForAdminSerializer = { serialize: sinon.stub() }; + dependencies = { userDetailsForAdminSerializer }; + }); + + it('gets the specified user', async function () { + // given + libUsecases.getUserDetailsForAdmin.withArgs({ userId: 123 }).resolves('userDetail'); + dependencies.userDetailsForAdminSerializer.serialize.withArgs('userDetail').returns('ok'); + + // when + const response = await userAdminController.getUserDetails(request, hFake, dependencies); + + // then + expect(response).to.be.equal('ok'); + }); + }); }); diff --git a/api/tests/unit/application/users/user-controller_test.js b/api/tests/unit/application/users/user-controller_test.js index 920d0f3e1d7..8953b3a08a7 100644 --- a/api/tests/unit/application/users/user-controller_test.js +++ b/api/tests/unit/application/users/user-controller_test.js @@ -4,6 +4,7 @@ import { DomainTransaction } from '../../../../lib/infrastructure/DomainTransact import { usecases as devcompUsecases } from '../../../../src/devcomp/domain/usecases/index.js'; import { evaluationUsecases } from '../../../../src/evaluation/domain/usecases/index.js'; import { NON_OIDC_IDENTITY_PROVIDERS } from '../../../../src/identity-access-management/domain/constants/identity-providers.js'; +import { usecases as identityAccessManagementUsecases } from '../../../../src/identity-access-management/domain/usecases/index.js'; import { UserOrganizationForAdmin } from '../../../../src/shared/domain/read-models/UserOrganizationForAdmin.js'; import * as requestResponseUtils from '../../../../src/shared/infrastructure/utils/request-response-utils.js'; import { domainBuilder, expect, hFake, sinon } from '../../../test-helper.js'; @@ -92,31 +93,6 @@ describe('Unit | Controller | user-controller', function () { }); }); - describe('#getUserDetailsForAdmin', function () { - let request; - let dependencies; - - beforeEach(function () { - request = { params: { id: 123 } }; - - sinon.stub(usecases, 'getUserDetailsForAdmin'); - const userDetailsForAdminSerializer = { serialize: sinon.stub() }; - dependencies = { userDetailsForAdminSerializer }; - }); - - it('should get the specified user for admin context', async function () { - // given - usecases.getUserDetailsForAdmin.withArgs({ userId: 123 }).resolves('userDetail'); - dependencies.userDetailsForAdminSerializer.serialize.withArgs('userDetail').returns('ok'); - - // when - const response = await userController.getUserDetailsForAdmin(request, hFake, dependencies); - - // then - expect(response).to.be.equal('ok'); - }); - }); - describe('#findPaginatedUserRecommendedTrainings', function () { it('should call the appropriate use-case', async function () { // given @@ -363,7 +339,7 @@ describe('Unit | Controller | user-controller', function () { knexTransaction: Symbol('transaction'), }; sinon.stub(usecases, 'anonymizeUser'); - sinon.stub(usecases, 'getUserDetailsForAdmin').resolves(userDetailsForAdmin); + sinon.stub(identityAccessManagementUsecases, 'getUserDetailsForAdmin').resolves(userDetailsForAdmin); sinon.stub(DomainTransaction, 'execute').callsFake((callback) => { return callback(domainTransaction); }); @@ -383,7 +359,7 @@ describe('Unit | Controller | user-controller', function () { // then expect(DomainTransaction.execute).to.have.been.called; expect(usecases.anonymizeUser).to.have.been.calledWithExactly({ userId, updatedByUserId, domainTransaction }); - expect(usecases.getUserDetailsForAdmin).to.have.been.calledWithExactly({ userId }); + expect(identityAccessManagementUsecases.getUserDetailsForAdmin).to.have.been.calledWithExactly({ userId }); expect(userAnonymizedDetailsForAdminSerializer.serialize).to.have.been.calledWithExactly(userDetailsForAdmin); expect(response.statusCode).to.equal(200); expect(response.source).to.deep.equal(anonymizedUserSerialized); From 1e47392b1616c90c73bf25899af2e6b7f9e946c2 Mon Sep 17 00:00:00 2001 From: Eric Lim Date: Mon, 4 Nov 2024 16:17:50 +0100 Subject: [PATCH 3/3] refactor(api): move get-user-details-for-admin usecase in src/identity-access-management --- .../domain/usecases/get-user-details-for-admin.js | 5 ----- .../application/user/user.admin.controller.js | 3 +-- .../usecases/get-user-details-for-admin.usecase.js | 12 ++++++++++++ .../application/user/user.admin.controller.test.js | 5 ++--- .../get-user-details-for-admin.usecase.test.js} | 8 ++++---- 5 files changed, 19 insertions(+), 14 deletions(-) delete mode 100644 api/lib/domain/usecases/get-user-details-for-admin.js create mode 100644 api/src/identity-access-management/domain/usecases/get-user-details-for-admin.usecase.js rename api/tests/{unit/domain/usecases/get-user-details-for-admin_test.js => identity-access-management/unit/domain/usecases/get-user-details-for-admin.usecase.test.js} (55%) diff --git a/api/lib/domain/usecases/get-user-details-for-admin.js b/api/lib/domain/usecases/get-user-details-for-admin.js deleted file mode 100644 index 021a445c0c1..00000000000 --- a/api/lib/domain/usecases/get-user-details-for-admin.js +++ /dev/null @@ -1,5 +0,0 @@ -const getUserDetailsForAdmin = async function ({ userId, userRepository }) { - return await userRepository.getUserDetailsForAdmin(userId); -}; - -export { getUserDetailsForAdmin }; diff --git a/api/src/identity-access-management/application/user/user.admin.controller.js b/api/src/identity-access-management/application/user/user.admin.controller.js index ceeb7c70b68..eacbc3e5829 100644 --- a/api/src/identity-access-management/application/user/user.admin.controller.js +++ b/api/src/identity-access-management/application/user/user.admin.controller.js @@ -1,4 +1,3 @@ -import { usecases as libUsecases } from '../../../../lib/domain/usecases/index.js'; import { usecases } from '../../domain/usecases/index.js'; import * as userDetailsForAdminSerializer from '../../infrastructure/serializers/jsonapi/user-details-for-admin.serializer.js'; import * as userForAdminSerializer from '../../infrastructure/serializers/jsonapi/user-for-admin.serializer.js'; @@ -59,7 +58,7 @@ const updateUserDetailsByAdmin = async function (request, h, dependencies = { us */ const getUserDetails = async function (request, h, dependencies = { userDetailsForAdminSerializer }) { const userId = request.params.id; - const userDetailsForAdmin = await libUsecases.getUserDetailsForAdmin({ userId }); + const userDetailsForAdmin = await usecases.getUserDetailsForAdmin({ userId }); return dependencies.userDetailsForAdminSerializer.serialize(userDetailsForAdmin); }; diff --git a/api/src/identity-access-management/domain/usecases/get-user-details-for-admin.usecase.js b/api/src/identity-access-management/domain/usecases/get-user-details-for-admin.usecase.js new file mode 100644 index 00000000000..60239687208 --- /dev/null +++ b/api/src/identity-access-management/domain/usecases/get-user-details-for-admin.usecase.js @@ -0,0 +1,12 @@ +/** + * @param {Object} params + * @param {string} params.userId + * @param {UserRepository} userRepository + * @throws UserNotFoundError + * @returns {Promise} + */ +const getUserDetailsForAdmin = async function ({ userId, userRepository }) { + return await userRepository.getUserDetailsForAdmin(userId); +}; + +export { getUserDetailsForAdmin }; diff --git a/api/tests/identity-access-management/unit/application/user/user.admin.controller.test.js b/api/tests/identity-access-management/unit/application/user/user.admin.controller.test.js index 1e0709bd6f2..08b08abb371 100644 --- a/api/tests/identity-access-management/unit/application/user/user.admin.controller.test.js +++ b/api/tests/identity-access-management/unit/application/user/user.admin.controller.test.js @@ -1,4 +1,3 @@ -import { usecases as libUsecases } from '../../../../../lib/domain/usecases/index.js'; import { userAdminController } from '../../../../../src/identity-access-management/application/user/user.admin.controller.js'; import { User } from '../../../../../src/identity-access-management/domain/models/User.js'; import { usecases } from '../../../../../src/identity-access-management/domain/usecases/index.js'; @@ -168,14 +167,14 @@ describe('Unit | Identity Access Management | Application | Controller | Admin | beforeEach(function () { request = { params: { id: 123 } }; - sinon.stub(libUsecases, 'getUserDetailsForAdmin'); + sinon.stub(usecases, 'getUserDetailsForAdmin'); const userDetailsForAdminSerializer = { serialize: sinon.stub() }; dependencies = { userDetailsForAdminSerializer }; }); it('gets the specified user', async function () { // given - libUsecases.getUserDetailsForAdmin.withArgs({ userId: 123 }).resolves('userDetail'); + usecases.getUserDetailsForAdmin.withArgs({ userId: 123 }).resolves('userDetail'); dependencies.userDetailsForAdminSerializer.serialize.withArgs('userDetail').returns('ok'); // when diff --git a/api/tests/unit/domain/usecases/get-user-details-for-admin_test.js b/api/tests/identity-access-management/unit/domain/usecases/get-user-details-for-admin.usecase.test.js similarity index 55% rename from api/tests/unit/domain/usecases/get-user-details-for-admin_test.js rename to api/tests/identity-access-management/unit/domain/usecases/get-user-details-for-admin.usecase.test.js index c87fdacc8ea..07f828fcf8a 100644 --- a/api/tests/unit/domain/usecases/get-user-details-for-admin_test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/get-user-details-for-admin.usecase.test.js @@ -1,14 +1,14 @@ -import { getUserDetailsForAdmin } from '../../../../lib/domain/usecases/get-user-details-for-admin.js'; -import { expect, sinon } from '../../../test-helper.js'; +import { getUserDetailsForAdmin } from '../../../../../src/identity-access-management/domain/usecases/get-user-details-for-admin.usecase.js'; +import { expect, sinon } from '../../../../test-helper.js'; -describe('Unit | UseCase | get-user-details-for-admin', function () { +describe('Unit | Identity Access Management | Domain | UseCase | get-user-details-for-admin', function () { let userRepository; beforeEach(function () { userRepository = { getUserDetailsForAdmin: sinon.stub() }; }); - it('should get the user details in administration context', async function () { + it('gets the user details in administration context', async function () { // given const userId = 1; const expectedUserDetailsForAdmin = { id: userId };