From def3fb5f8d445f6555fa6a7f5688c9e41f358da6 Mon Sep 17 00:00:00 2001 From: Guillaume Olejniczak Date: Mon, 23 Sep 2024 17:39:40 +0200 Subject: [PATCH] WIP --- api/lib/application/users/index.js | 51 --------------- api/lib/application/users/user-controller.js | 19 ------ api/server.js | 2 + api/src/profile/application/index.js | 64 ++++++++++++++++++ .../profile/application/profile-controller.js | 26 ++++++++ .../domain/usecases/get-user-profile.js | 4 +- api/src/profile/domain/usecases/index.js | 8 +++ .../serializers/jsonapi/profile-serializer.js | 0 api/src/profile/routes.js | 5 ++ .../profile/unit/application/index_test.js | 27 ++++++++ .../application/profile-controller_test.js | 41 ++++++++++++ .../domain/usecases/get-user-profile_test.js | 10 +-- .../jsonapi/profile-serializer_test.js | 6 +- .../unit/application/users/index_test.js | 21 ------ .../application/users/user-controller_test.js | 65 ------------------- 15 files changed, 183 insertions(+), 166 deletions(-) create mode 100644 api/src/profile/application/index.js create mode 100644 api/src/profile/application/profile-controller.js rename api/{lib => src/profile}/domain/usecases/get-user-profile.js (90%) rename api/{lib => src/profile}/infrastructure/serializers/jsonapi/profile-serializer.js (100%) create mode 100644 api/src/profile/routes.js create mode 100644 api/tests/profile/unit/application/index_test.js create mode 100644 api/tests/profile/unit/application/profile-controller_test.js rename api/tests/{ => profile}/unit/domain/usecases/get-user-profile_test.js (94%) rename api/tests/{ => profile}/unit/infrastructure/serializers/jsonapi/profile-serializer_test.js (94%) diff --git a/api/lib/application/users/index.js b/api/lib/application/users/index.js index 48a6b2b3680..d343d01d44b 100644 --- a/api/lib/application/users/index.js +++ b/api/lib/application/users/index.js @@ -40,33 +40,6 @@ const register = async function (server) { tags: ['api', 'admin', 'user'], }, }, - { - method: 'GET', - path: '/api/admin/users/{id}/profile', - config: { - pre: [ - { - method: (request, h) => - securityPreHandlers.hasAtLeastOneAccessOf([ - securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, - securityPreHandlers.checkAdminMemberHasRoleCertif, - securityPreHandlers.checkAdminMemberHasRoleSupport, - securityPreHandlers.checkAdminMemberHasRoleMetier, - ])(request, h), - }, - ], - validate: { - params: Joi.object({ - id: identifiersType.userId, - }), - }, - handler: userController.getProfileForAdmin, - notes: [ - "- Permet à un administrateur de récupérer le nombre total de Pix d'un utilisateur\n et de ses scorecards", - ], - tags: ['api', 'user', 'profile'], - }, - }, { method: 'GET', path: '/api/admin/users/{id}/organizations', @@ -351,30 +324,6 @@ const register = async function (server) { tags: ['api'], }, }, - { - method: 'GET', - path: '/api/users/{id}/profile', - config: { - pre: [ - { - method: securityPreHandlers.checkRequestedUserIsAuthenticatedUser, - assign: 'requestedUserIsAuthenticatedUser', - }, - ], - validate: { - params: Joi.object({ - id: identifiersType.userId, - }), - }, - handler: userController.getProfile, - notes: [ - '- **Cette route est restreinte aux utilisateurs authentifiés**\n' + - "- Récupération du nombre total de Pix de l'utilisateur\n et de ses scorecards" + - '- L’id demandé doit correspondre à celui de l’utilisateur authentifié', - ], - tags: ['api', 'user', 'profile'], - }, - }, { method: 'GET', path: '/api/users/{userId}/campaigns/{campaignId}/profile', diff --git a/api/lib/application/users/user-controller.js b/api/lib/application/users/user-controller.js index 427667c12d4..b9349c7bb35 100644 --- a/api/lib/application/users/user-controller.js +++ b/api/lib/application/users/user-controller.js @@ -10,7 +10,6 @@ import { DomainTransaction } from '../../infrastructure/DomainTransaction.js'; import * as campaignParticipationOverviewSerializer from '../../infrastructure/serializers/jsonapi/campaign-participation-overview-serializer.js'; import * as certificationCenterMembershipSerializer from '../../infrastructure/serializers/jsonapi/certification-center-membership-serializer.js'; import * as participantResultSerializer from '../../infrastructure/serializers/jsonapi/participant-result-serializer.js'; -import * as profileSerializer from '../../infrastructure/serializers/jsonapi/profile-serializer.js'; import * as sharedProfileForCampaignSerializer from '../../infrastructure/serializers/jsonapi/shared-profile-for-campaign-serializer.js'; import * as userAnonymizedDetailsForAdminSerializer from '../../infrastructure/serializers/jsonapi/user-anonymized-details-for-admin-serializer.js'; import * as userDetailsForAdminSerializer from '../../infrastructure/serializers/jsonapi/user-details-for-admin-serializer.js'; @@ -112,22 +111,6 @@ const getCampaignParticipationOverviews = async function ( ); }; -const getProfile = function (request, h, dependencies = { profileSerializer, requestResponseUtils }) { - const authenticatedUserId = request.auth.credentials.userId; - const locale = dependencies.requestResponseUtils.extractLocaleFromRequest(request); - - return usecases - .getUserProfile({ userId: authenticatedUserId, locale }) - .then(dependencies.profileSerializer.serialize); -}; - -const getProfileForAdmin = function (request, h, dependencies = { profileSerializer, requestResponseUtils }) { - const userId = request.params.id; - const locale = dependencies.requestResponseUtils.extractLocaleFromRequest(request); - - return usecases.getUserProfile({ userId, locale }).then(dependencies.profileSerializer.serialize); -}; - const resetScorecard = function (request, h, dependencies = { scorecardSerializer, requestResponseUtils }) { const authenticatedUserId = request.auth.credentials.userId; const competenceId = request.params.competenceId; @@ -279,8 +262,6 @@ const userController = { findUserOrganizationsForAdmin, getCampaignParticipationOverviews, getCampaignParticipations, - getProfile, - getProfileForAdmin, getUserCampaignAssessmentResult, getUserCampaignParticipationToCampaign, getUserDetailsForAdmin, diff --git a/api/server.js b/api/server.js index 91c1d1e46d5..a67b048dbbe 100644 --- a/api/server.js +++ b/api/server.js @@ -26,6 +26,7 @@ import { learnerManagementRoutes } from './src/prescription/learner-management/r import { organizationLearnerRoutes } from './src/prescription/organization-learner/routes.js'; import { organizationPlaceRoutes } from './src/prescription/organization-place/routes.js'; import { targetProfileRoutes } from './src/prescription/target-profile/routes.js'; +import { profileRoutes } from './src/profile/routes.js'; import { schoolRoutes } from './src/school/routes.js'; import { config } from './src/shared/config.js'; import { monitoringTools } from './src/shared/infrastructure/monitoring-tools.js'; @@ -150,6 +151,7 @@ const setupRoutesAndPlugins = async function (server) { identityAccessManagementRoutes, organizationalEntitiesRoutes, sharedRoutes, + profileRoutes, evaluationRoutes, flashCertificationRoutes, devcompRoutes, diff --git a/api/src/profile/application/index.js b/api/src/profile/application/index.js new file mode 100644 index 00000000000..6bb2075285c --- /dev/null +++ b/api/src/profile/application/index.js @@ -0,0 +1,64 @@ +import Joi from 'joi'; + +import { securityPreHandlers } from '../../../src/shared/application/security-pre-handlers.js'; +import { identifiersType } from '../../../src/shared/domain/types/identifiers-type.js'; +import { profileController } from './profile-controller.js'; + +const register = async function (server) { + server.route([ + { + method: 'GET', + path: '/api/users/{id}/profile', + config: { + pre: [ + { + method: securityPreHandlers.checkRequestedUserIsAuthenticatedUser, + assign: 'requestedUserIsAuthenticatedUser', + }, + ], + validate: { + params: Joi.object({ + id: identifiersType.userId, + }), + }, + handler: profileController.getProfile, + notes: [ + '- **Cette route est restreinte aux utilisateurs authentifiés**\n' + + "- Récupération du nombre total de Pix de l'utilisateur\n et de ses scorecards" + + '- L’id demandé doit correspondre à celui de l’utilisateur authentifié', + ], + tags: ['api', 'user', 'profile'], + }, + }, + { + method: 'GET', + path: '/api/admin/users/{id}/profile', + config: { + pre: [ + { + method: (request, h) => + securityPreHandlers.hasAtLeastOneAccessOf([ + securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, + securityPreHandlers.checkAdminMemberHasRoleCertif, + securityPreHandlers.checkAdminMemberHasRoleSupport, + securityPreHandlers.checkAdminMemberHasRoleMetier, + ])(request, h), + }, + ], + validate: { + params: Joi.object({ + id: identifiersType.userId, + }), + }, + handler: profileController.getProfileForAdmin, + notes: [ + "- Permet à un administrateur de récupérer le nombre total de Pix d'un utilisateur\n et de ses scorecards", + ], + tags: ['api', 'user', 'profile'], + }, + }, + ]); +}; + +const name = 'profile-api'; +export { name, register }; diff --git a/api/src/profile/application/profile-controller.js b/api/src/profile/application/profile-controller.js new file mode 100644 index 00000000000..14c26971a48 --- /dev/null +++ b/api/src/profile/application/profile-controller.js @@ -0,0 +1,26 @@ +import * as requestResponseUtils from '../../../src/shared/infrastructure/utils/request-response-utils.js'; +import { usecases } from '../domain/usecases/index.js'; +import * as profileSerializer from '../infrastructure/serializers/jsonapi/profile-serializer.js'; + +const getProfile = function (request, h, dependencies = { profileSerializer, requestResponseUtils }) { + const authenticatedUserId = request.auth.credentials.userId; + const locale = dependencies.requestResponseUtils.extractLocaleFromRequest(request); + + return usecases + .getUserProfile({ userId: authenticatedUserId, locale }) + .then(dependencies.profileSerializer.serialize); +}; + +const getProfileForAdmin = function (request, h, dependencies = { profileSerializer, requestResponseUtils }) { + const userId = request.params.id; + const locale = dependencies.requestResponseUtils.extractLocaleFromRequest(request); + + return usecases.getUserProfile({ userId, locale }).then(dependencies.profileSerializer.serialize); +}; + +const profileController = { + getProfile, + getProfileForAdmin, +}; + +export { profileController }; diff --git a/api/lib/domain/usecases/get-user-profile.js b/api/src/profile/domain/usecases/get-user-profile.js similarity index 90% rename from api/lib/domain/usecases/get-user-profile.js rename to api/src/profile/domain/usecases/get-user-profile.js index 8f9208eb973..bb98b735675 100644 --- a/api/lib/domain/usecases/get-user-profile.js +++ b/api/src/profile/domain/usecases/get-user-profile.js @@ -1,7 +1,7 @@ import _ from 'lodash'; -import { Scorecard } from '../../../src/evaluation/domain/models/Scorecard.js'; -import { constants } from '../../../src/shared/domain/constants.js'; +import { Scorecard } from '../../../evaluation/domain/models/Scorecard.js'; +import { constants } from '../../../shared/domain/constants.js'; const getUserProfile = async function ({ userId, diff --git a/api/src/profile/domain/usecases/index.js b/api/src/profile/domain/usecases/index.js index 27a134b70cb..c3e1f28a02b 100644 --- a/api/src/profile/domain/usecases/index.js +++ b/api/src/profile/domain/usecases/index.js @@ -1,7 +1,11 @@ import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import * as knowledgeElementRepository from '../../../../lib/infrastructure/repositories/knowledge-element-repository.js'; +import * as competenceEvaluationRepository from '../../../evaluation/infrastructure/repositories/competence-evaluation-repository.js'; import * as profileRewardRepository from '../../../profile/infrastructure/repositories/profile-reward-repository.js'; +import * as areaRepository from '../../../shared/infrastructure/repositories/area-repository.js'; +import * as competenceRepository from '../../../shared/infrastructure/repositories/competence-repository.js'; import { injectDependencies } from '../../../shared/infrastructure/utils/dependency-injection.js'; import { importNamedExportsFromDirectory } from '../../../shared/infrastructure/utils/import-named-exports-from-directory.js'; @@ -12,6 +16,10 @@ const usecasesWithoutInjectedDependencies = { }; const dependencies = { + competenceRepository, + areaRepository, + competenceEvaluationRepository, + knowledgeElementRepository, profileRewardRepository, }; diff --git a/api/lib/infrastructure/serializers/jsonapi/profile-serializer.js b/api/src/profile/infrastructure/serializers/jsonapi/profile-serializer.js similarity index 100% rename from api/lib/infrastructure/serializers/jsonapi/profile-serializer.js rename to api/src/profile/infrastructure/serializers/jsonapi/profile-serializer.js diff --git a/api/src/profile/routes.js b/api/src/profile/routes.js new file mode 100644 index 00000000000..e747f54abe0 --- /dev/null +++ b/api/src/profile/routes.js @@ -0,0 +1,5 @@ +import * as profileRoute from './application/index.js'; + +const profileRoutes = [profileRoute]; + +export { profileRoutes }; diff --git a/api/tests/profile/unit/application/index_test.js b/api/tests/profile/unit/application/index_test.js new file mode 100644 index 00000000000..b2d0228be88 --- /dev/null +++ b/api/tests/profile/unit/application/index_test.js @@ -0,0 +1,27 @@ +import * as moduleUnderTest from '../../../../src/profile/application/index.js'; +import { profileController } from '../../../../src/profile/application/profile-controller.js'; +import { securityPreHandlers } from '../../../../src/shared/application/security-pre-handlers.js'; +import { HttpTestServer, sinon } from '../../../../tests/test-helper.js'; + +describe('Unit | Router | user-router', function () { + describe('GET /api/users/{id}/profile', function () { + const method = 'GET'; + const url = '/api/users/42/profile'; + + it('exists', async function () { + // given + sinon.stub(profileController, 'getProfile').returns('ok'); + sinon + .stub(securityPreHandlers, 'checkRequestedUserIsAuthenticatedUser') + .callsFake((request, h) => h.response(true)); + const httpTestServer = new HttpTestServer(); + await httpTestServer.register(moduleUnderTest); + + // when + await httpTestServer.request(method, url); + + // then + sinon.assert.calledOnce(profileController.getProfile); + }); + }); +}); diff --git a/api/tests/profile/unit/application/profile-controller_test.js b/api/tests/profile/unit/application/profile-controller_test.js new file mode 100644 index 00000000000..f4cdcb25b2b --- /dev/null +++ b/api/tests/profile/unit/application/profile-controller_test.js @@ -0,0 +1,41 @@ +import { profileController } from '../../../../src/profile/application/profile-controller.js'; +import { usecases } from '../../../../src/profile/domain/usecases/index.js'; +import * as requestResponseUtils from '../../../../src/shared/infrastructure/utils/request-response-utils.js'; +import { expect, hFake, sinon } from '../../../test-helper.js'; + +describe('Profile | Unit | Controller | profile-controller', function () { + describe('#getProfile', function () { + beforeEach(function () { + sinon.stub(usecases, 'getUserProfile').resolves({ + pixScore: 3, + scorecards: [], + }); + }); + + it('should call the expected usecase', async function () { + // given + const profileSerializer = { serialize: sinon.stub() }; + profileSerializer.serialize.resolves(); + const userId = '12'; + const locale = 'fr'; + + const request = { + auth: { + credentials: { + userId, + }, + }, + params: { + id: userId, + }, + headers: { 'accept-language': locale }, + }; + + // when + await profileController.getProfile(request, hFake, { profileSerializer, requestResponseUtils }); + + // then + expect(usecases.getUserProfile).to.have.been.calledWithExactly({ userId, locale }); + }); + }); +}); diff --git a/api/tests/unit/domain/usecases/get-user-profile_test.js b/api/tests/profile/unit/domain/usecases/get-user-profile_test.js similarity index 94% rename from api/tests/unit/domain/usecases/get-user-profile_test.js rename to api/tests/profile/unit/domain/usecases/get-user-profile_test.js index 4f8e0ca9e7c..951c250774b 100644 --- a/api/tests/unit/domain/usecases/get-user-profile_test.js +++ b/api/tests/profile/unit/domain/usecases/get-user-profile_test.js @@ -1,9 +1,9 @@ import _ from 'lodash'; -import { getUserProfile } from '../../../../lib/domain/usecases/get-user-profile.js'; -import { Scorecard } from '../../../../src/evaluation/domain/models/Scorecard.js'; -import { constants } from '../../../../src/shared/domain/constants.js'; -import { domainBuilder, expect, sinon } from '../../../test-helper.js'; +import { Scorecard } from '../../../../../src/evaluation/domain/models/Scorecard.js'; +import { getUserProfile } from '../../../../../src/profile/domain/usecases/get-user-profile.js'; +import { constants } from '../../../../../src/shared/domain/constants.js'; +import { domainBuilder, expect, sinon } from '../../../../test-helper.js'; function assertScorecard(userScorecard, expectedUserScorecard) { expect(userScorecard.earnedPix).to.equal(expectedUserScorecard.earnedPix); @@ -12,7 +12,7 @@ function assertScorecard(userScorecard, expectedUserScorecard) { expect(userScorecard.status).to.equal(expectedUserScorecard.status); } -describe('Unit | UseCase | get-user-profile', function () { +describe('Profile | Unit | UseCase | get-user-profile', function () { let competenceRepository; let areaRepository; let knowledgeElementRepository; diff --git a/api/tests/unit/infrastructure/serializers/jsonapi/profile-serializer_test.js b/api/tests/profile/unit/infrastructure/serializers/jsonapi/profile-serializer_test.js similarity index 94% rename from api/tests/unit/infrastructure/serializers/jsonapi/profile-serializer_test.js rename to api/tests/profile/unit/infrastructure/serializers/jsonapi/profile-serializer_test.js index 1ba45d88b9c..21e0f1c91a1 100644 --- a/api/tests/unit/infrastructure/serializers/jsonapi/profile-serializer_test.js +++ b/api/tests/profile/unit/infrastructure/serializers/jsonapi/profile-serializer_test.js @@ -1,6 +1,6 @@ -import * as serializer from '../../../../../lib/infrastructure/serializers/jsonapi/profile-serializer.js'; -import { MAX_REACHABLE_LEVEL } from '../../../../../src/shared/domain/constants.js'; -import { domainBuilder, expect } from '../../../../test-helper.js'; +import * as serializer from '../../../../../../src/profile/infrastructure/serializers/jsonapi/profile-serializer.js'; +import { MAX_REACHABLE_LEVEL } from '../../../../../../src/shared/domain/constants.js'; +import { domainBuilder, expect } from '../../../../../test-helper.js'; describe('Unit | Serializer | JSONAPI | profile', function () { describe('#serialize()', function () { diff --git a/api/tests/unit/application/users/index_test.js b/api/tests/unit/application/users/index_test.js index e9e7c3c8f84..ad9efca25ca 100644 --- a/api/tests/unit/application/users/index_test.js +++ b/api/tests/unit/application/users/index_test.js @@ -10,27 +10,6 @@ const CODE_IDENTITY_PROVIDER_POLE_EMPLOI = OidcIdentityProviders.POLE_EMPLOI.cod const oidcProviderCode = 'genericOidcProviderCode'; describe('Unit | Router | user-router', function () { - describe('GET /api/users/{id}/profile', function () { - const method = 'GET'; - const url = '/api/users/42/profile'; - - it('exists', async function () { - // given - sinon.stub(userController, 'getProfile').returns('ok'); - sinon - .stub(securityPreHandlers, 'checkRequestedUserIsAuthenticatedUser') - .callsFake((request, h) => h.response(true)); - const httpTestServer = new HttpTestServer(); - await httpTestServer.register(moduleUnderTest); - - // when - await httpTestServer.request(method, url); - - // then - sinon.assert.calledOnce(userController.getProfile); - }); - }); - describe('GET /api/users/{userId}/campaigns/{campaignId}/profile', function () { const method = 'GET'; diff --git a/api/tests/unit/application/users/user-controller_test.js b/api/tests/unit/application/users/user-controller_test.js index 476e8f595f0..7552e34cead 100644 --- a/api/tests/unit/application/users/user-controller_test.js +++ b/api/tests/unit/application/users/user-controller_test.js @@ -375,71 +375,6 @@ describe('Unit | Controller | user-controller', function () { }); }); - describe('#getProfile', function () { - beforeEach(function () { - sinon.stub(usecases, 'getUserProfile').resolves({ - pixScore: 3, - scorecards: [], - }); - }); - - it('should call the expected usecase', async function () { - // given - const profileSerializer = { serialize: sinon.stub() }; - profileSerializer.serialize.resolves(); - const userId = '12'; - const locale = 'fr'; - - const request = { - auth: { - credentials: { - userId, - }, - }, - params: { - id: userId, - }, - headers: { 'accept-language': locale }, - }; - - // when - await userController.getProfile(request, hFake, { profileSerializer, requestResponseUtils }); - - // then - expect(usecases.getUserProfile).to.have.been.calledWithExactly({ userId, locale }); - }); - }); - - describe('#getProfileForAdmin', function () { - beforeEach(function () { - sinon.stub(usecases, 'getUserProfile').resolves({ - pixScore: 3, - scorecards: [], - }); - }); - - it('should call the expected usecase', async function () { - // given - const profileSerializer = { serialize: sinon.stub() }; - profileSerializer.serialize.resolves(); - const userId = '12'; - const locale = 'fr'; - - const request = { - params: { - id: userId, - }, - headers: { 'accept-language': locale }, - }; - - // when - await userController.getProfileForAdmin(request, hFake, { profileSerializer, requestResponseUtils }); - - // then - expect(usecases.getUserProfile).to.have.been.calledWithExactly({ userId, locale }); - }); - }); - describe('#resetScorecard', function () { beforeEach(function () { sinon.stub(evaluationUsecases, 'resetScorecard').resolves({