diff --git a/api/src/profile/application/index.js b/api/src/profile/application/index.js index 23252b58463..5f429ab8881 100644 --- a/api/src/profile/application/index.js +++ b/api/src/profile/application/index.js @@ -6,7 +6,37 @@ import { attestationController } from './attestation-controller.js'; import { profileController } from './profile-controller.js'; const register = async function (server) { - server.route([ + const adminRoutes = [ + { + 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 userRoutes = [ { method: 'GET', path: '/api/users/{userId}/attestations/{attestationKey}', @@ -57,33 +87,38 @@ const register = async function (server) { }, }, { - method: 'GET', - path: '/api/admin/users/{id}/profile', + method: 'POST', + path: '/api/users/{userId}/profile/share-reward', config: { pre: [ { - method: (request, h) => - securityPreHandlers.hasAtLeastOneAccessOf([ - securityPreHandlers.checkAdminMemberHasRoleSuperAdmin, - securityPreHandlers.checkAdminMemberHasRoleCertif, - securityPreHandlers.checkAdminMemberHasRoleSupport, - securityPreHandlers.checkAdminMemberHasRoleMetier, - ])(request, h), + method: securityPreHandlers.checkRequestedUserIsAuthenticatedUser, + assign: 'requestedUserIsAuthenticatedUser', }, ], validate: { params: Joi.object({ - id: identifiersType.userId, + userId: identifiersType.userId, + }), + payload: Joi.object({ + data: { + attributes: { + campaignParticipationId: identifiersType.campaignParticipationId, + profileRewardId: identifiersType.profileRewardId, + }, + }, }), }, - handler: profileController.getProfileForAdmin, + handler: profileController.shareProfileReward, notes: [ - "- Permet à un administrateur de récupérer le nombre total de Pix d'un utilisateur\n et de ses scorecards", + "- Cette route permet à un utilisateur de partager l'obtention de son attestation avec une organisation\n", ], - tags: ['api', 'user', 'profile'], + tags: ['api', 'user', 'profile', 'reward'], }, }, - ]); + ]; + + server.route([...adminRoutes, ...userRoutes]); }; const name = 'profile-api'; diff --git a/api/src/profile/application/profile-controller.js b/api/src/profile/application/profile-controller.js index 14c26971a48..c675f0b40d1 100644 --- a/api/src/profile/application/profile-controller.js +++ b/api/src/profile/application/profile-controller.js @@ -18,9 +18,18 @@ const getProfileForAdmin = function (request, h, dependencies = { profileSeriali return usecases.getUserProfile({ userId, locale }).then(dependencies.profileSerializer.serialize); }; +const shareProfileReward = async function (request, h) { + const userId = request.params.userId; + const { profileRewardId, campaignParticipationId } = request.payload.data.attributes; + + await usecases.shareProfileReward({ userId, profileRewardId, campaignParticipationId }); + return h.response().code(201); +}; + const profileController = { getProfile, getProfileForAdmin, + shareProfileReward, }; export { profileController }; diff --git a/api/src/shared/domain/types/identifiers-type.js b/api/src/shared/domain/types/identifiers-type.js index 98a1339687d..273cd84f3dc 100644 --- a/api/src/shared/domain/types/identifiers-type.js +++ b/api/src/shared/domain/types/identifiers-type.js @@ -55,6 +55,7 @@ const typesPositiveInteger32bits = [ 'ownerId', 'passageId', 'placeId', + 'profileRewardId', 'schoolingRegistrationId', 'sessionId', 'stageCollectionId', diff --git a/api/tests/profile/acceptance/application/share-profile-reward-route_test.js b/api/tests/profile/acceptance/application/share-profile-reward-route_test.js new file mode 100644 index 00000000000..4f86897195a --- /dev/null +++ b/api/tests/profile/acceptance/application/share-profile-reward-route_test.js @@ -0,0 +1,76 @@ +import { + createServer, + databaseBuilder, + expect, + generateValidRequestAuthorizationHeader, +} from '../../../test-helper.js'; + +describe('Profile | Acceptance | Application | Share Profile Route ', function () { + let server; + + beforeEach(async function () { + server = await createServer(); + }); + + describe('POST /api/users/{userId}/profile/share-reward', function () { + describe('when profile reward exists and is linked to user', function () { + it('should return a success code', async function () { + // given + const userId = databaseBuilder.factory.buildUser().id; + const profileReward = databaseBuilder.factory.buildProfileReward({ userId }); + const campaignParticipation = databaseBuilder.factory.buildCampaignParticipation(); + + await databaseBuilder.commit(); + const options = { + method: 'POST', + headers: { authorization: generateValidRequestAuthorizationHeader(userId) }, + url: `/api/users/${userId}/profile/share-reward`, + payload: { + data: { + attributes: { + profileRewardId: profileReward.id, + campaignParticipationId: campaignParticipation.id, + }, + }, + }, + }; + + // when + const response = await server.inject(options); + + // then + expect(response.statusCode).to.equal(201); + }); + }); + }); + + describe('when profile reward is not linked to user', function () { + it('should return a 412 code', async function () { + // given + const userId = databaseBuilder.factory.buildUser().id; + const profileReward = databaseBuilder.factory.buildProfileReward(); + const campaignParticipation = databaseBuilder.factory.buildCampaignParticipation(); + + await databaseBuilder.commit(); + const options = { + method: 'POST', + headers: { authorization: generateValidRequestAuthorizationHeader(userId) }, + url: `/api/users/${userId}/profile/share-reward`, + payload: { + data: { + attributes: { + profileRewardId: profileReward.id, + campaignParticipationId: campaignParticipation.id, + }, + }, + }, + }; + + // when + const response = await server.inject(options); + + // then + expect(response.statusCode).to.equal(412); + }); + }); +}); diff --git a/api/tests/profile/unit/application/profile-controller_test.js b/api/tests/profile/unit/application/profile-controller_test.js index f4cdcb25b2b..7b214b001c6 100644 --- a/api/tests/profile/unit/application/profile-controller_test.js +++ b/api/tests/profile/unit/application/profile-controller_test.js @@ -38,4 +38,46 @@ describe('Profile | Unit | Controller | profile-controller', function () { expect(usecases.getUserProfile).to.have.been.calledWithExactly({ userId, locale }); }); }); + + describe('#shareProfileReward', function () { + beforeEach(function () { + sinon.stub(usecases, 'shareProfileReward').resolves(); + }); + + it('should call the expected usecase', async function () { + // given + const profileRewardId = '11'; + const campaignParticipationId = '22'; + const userId = '33'; + + const request = { + auth: { + credentials: { + userId, + }, + }, + params: { + userId, + }, + payload: { + data: { + attributes: { + profileRewardId, + campaignParticipationId, + }, + }, + }, + }; + + // when + await profileController.shareProfileReward(request, hFake); + + // then + expect(usecases.shareProfileReward).to.have.been.calledWithExactly({ + userId, + profileRewardId, + campaignParticipationId, + }); + }); + }); });