Skip to content

Commit 239e83d

Browse files
[FEATURE] Ajout d'une route pour récupérer les infos de début de parcours (PIX-14813).
#10352
2 parents ebf5209 + 5e4c6bc commit 239e83d

File tree

10 files changed

+522
-1
lines changed

10 files changed

+522
-1
lines changed

api/src/prescription/campaign/application/campaign-controller.js

+15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as campaignAnalysisSerializer from '../../campaign-participation/infras
33
import { usecases } from '../domain/usecases/index.js';
44
import * as divisionSerializer from '../infrastructure/serializers/jsonapi/division-serializer.js';
55
import * as groupSerializer from '../infrastructure/serializers/jsonapi/group-serializer.js';
6+
import * as presentationStepsSerializer from '../infrastructure/serializers/jsonapi/presentation-steps-serializer.js';
67

78
const division = async function (request) {
89
const { userId } = request.auth.credentials;
@@ -28,10 +29,24 @@ const getAnalysis = async function (request, h, dependencies = { campaignAnalysi
2829
return dependencies.campaignAnalysisSerializer.serialize(campaignAnalysis);
2930
};
3031

32+
const getPresentationSteps = async function (
33+
request,
34+
_,
35+
dependencies = { presentationStepsSerializer, extractLocaleFromRequest },
36+
) {
37+
const { userId } = request.auth.credentials;
38+
const campaignCode = request.params.campaignCode;
39+
const locale = dependencies.extractLocaleFromRequest(request);
40+
41+
const presentationSteps = await usecases.getPresentationSteps({ userId, campaignCode, locale });
42+
return dependencies.presentationStepsSerializer.serialize(presentationSteps);
43+
};
44+
3145
const campaignController = {
3246
division,
3347
getAnalysis,
3448
getGroups,
49+
getPresentationSteps,
3550
};
3651

3752
export { campaignController };

api/src/prescription/campaign/application/campaign-route.js

+18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Joi from 'joi';
22

33
import { identifiersType } from '../../../shared/domain/types/identifiers-type.js';
4+
import { identifiersType as prescriptionIdentifiersType } from '../../shared/domain/types/identifiers-type.js';
45
import { campaignController } from './campaign-controller.js';
56

67
const register = async function (server) {
@@ -56,6 +57,23 @@ const register = async function (server) {
5657
tags: ['api', 'campaign'],
5758
},
5859
},
60+
{
61+
method: 'GET',
62+
path: '/api/campaigns/{campaignCode}/presentation-steps',
63+
config: {
64+
handler: campaignController.getPresentationSteps,
65+
validate: {
66+
params: Joi.object({
67+
campaignCode: prescriptionIdentifiersType.campaignCode,
68+
}),
69+
},
70+
notes: [
71+
'- **Cette route est restreinte aux utilisateurs authentifiés**\n' +
72+
'- Récupération des infos des écrans de présentation de début de parcours',
73+
],
74+
tags: ['api', 'campaign', 'presentation'],
75+
},
76+
},
5977
]);
6078
};
6179

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { CampaignCodeError, UserNotAuthorizedToAccessEntityError } from '../../../../shared/domain/errors.js';
2+
import { ArchivedCampaignError, DeletedCampaignError } from '../errors.js';
3+
4+
const getPresentationSteps = async function ({
5+
userId,
6+
campaignCode,
7+
locale,
8+
badgeRepository,
9+
campaignRepository,
10+
learningContentRepository,
11+
}) {
12+
const campaign = await campaignRepository.getByCode(campaignCode);
13+
14+
if (!campaign) throw new CampaignCodeError();
15+
if (campaign.archivedAt) throw new ArchivedCampaignError();
16+
if (campaign.deletedAt) throw new DeletedCampaignError();
17+
18+
const hasUserAccessToResult = await campaignRepository.checkIfUserOrganizationHasAccessToCampaign(
19+
campaign.id,
20+
userId,
21+
);
22+
if (!hasUserAccessToResult)
23+
throw new UserNotAuthorizedToAccessEntityError('User does not have access to this campaign');
24+
25+
const campaignBadges = await badgeRepository.findByCampaignId(campaign.id);
26+
const learningContent = await learningContentRepository.findByCampaignId(campaign.id, locale);
27+
28+
return {
29+
customLandingPageText: campaign.customLandingPageText,
30+
badges: campaignBadges,
31+
competences: learningContent?.competences,
32+
};
33+
};
34+
35+
export { getPresentationSteps };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import jsonapiSerializer from 'jsonapi-serializer';
2+
3+
const { Serializer } = jsonapiSerializer;
4+
5+
const serialize = function (presentationSteps) {
6+
return new Serializer('campaign-presentation-step', {
7+
attributes: ['customLandingPageText', 'badges', 'competences'],
8+
badges: {
9+
ref: 'id',
10+
attributes: ['altMessage', 'imageUrl', 'isAlwaysVisible', 'isCertifiable', 'key', 'message', 'title'],
11+
},
12+
competences: {
13+
ref: 'id',
14+
attributes: ['index', 'name'],
15+
},
16+
}).serialize(presentationSteps);
17+
};
18+
19+
export { serialize };

api/src/prescription/shared/domain/types/identifiers-type.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ import Joi from 'joi';
33
import { categories } from '../../../../shared/domain/models/TargetProfile.js';
44
import { certificabilityByLabel } from '../../application/helpers.js';
55

6+
const identifiersType = {
7+
campaignCode: Joi.string().min(9).max(9).required(),
8+
};
9+
610
const filterType = {
711
certificability: Joi.string().valid(...Object.keys(certificabilityByLabel)),
812
targetProfileCategory: Joi.string().valid(...Object.values(categories)),
913
};
1014

11-
export { filterType };
15+
export { filterType, identifiersType };

api/tests/prescription/campaign/acceptance/application/campaign-route_test.js

+93
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Membership } from '../../../../../src/shared/domain/models/Membership.j
22
import {
33
createServer,
44
databaseBuilder,
5+
domainBuilder,
56
expect,
67
generateValidRequestAuthorizationHeader,
78
learningContentBuilder,
@@ -350,4 +351,96 @@ describe('Acceptance | API | Campaign Route', function () {
350351
});
351352
});
352353
});
354+
355+
describe('GET /api/campaigns/{campaignId}/presentation-steps', function () {
356+
it('should return the presentation steps informations', async function () {
357+
// given
358+
const userId = databaseBuilder.factory.buildUser().id;
359+
const organization = databaseBuilder.factory.buildOrganization();
360+
361+
databaseBuilder.factory.buildMembership({
362+
userId,
363+
organizationId: organization.id,
364+
organizationRole: Membership.roles.MEMBER,
365+
});
366+
367+
const targetProfile = databaseBuilder.factory.buildTargetProfile({ organizationId: organization.id });
368+
const badge = databaseBuilder.factory.buildBadge({ targetProfileId: targetProfile.id });
369+
const campaign = databaseBuilder.factory.buildCampaign({
370+
code: 'CAMPAIGN1',
371+
customLandingPageText: 'landing page text',
372+
targetProfileId: targetProfile.id,
373+
organizationId: organization.id,
374+
});
375+
376+
const learningContent = domainBuilder.buildLearningContent.withSimpleContent();
377+
const learningContentObjects = learningContentBuilder.fromAreas(learningContent.frameworks[0].areas);
378+
mockLearningContent(learningContentObjects);
379+
380+
databaseBuilder.factory.buildCampaignSkill({
381+
campaignId: campaign.id,
382+
skillId: learningContentObjects.competences[0].skillIds[0],
383+
});
384+
await databaseBuilder.commit();
385+
386+
// when
387+
const options = {
388+
method: 'GET',
389+
url: `/api/campaigns/${campaign.code}/presentation-steps`,
390+
headers: { authorization: generateValidRequestAuthorizationHeader(userId) },
391+
};
392+
393+
const response = await server.inject(options);
394+
395+
// then
396+
expect(response.statusCode).to.equal(200);
397+
expect(response.result).to.deep.equal({
398+
data: {
399+
type: 'campaign-presentation-steps',
400+
attributes: { 'custom-landing-page-text': campaign.customLandingPageText },
401+
relationships: {
402+
badges: {
403+
data: [
404+
{
405+
id: String(badge.id),
406+
type: 'badges',
407+
},
408+
],
409+
},
410+
competences: {
411+
data: [
412+
{
413+
id: learningContentObjects.competences[0].id,
414+
type: 'competences',
415+
},
416+
],
417+
},
418+
},
419+
},
420+
included: [
421+
{
422+
type: 'badges',
423+
id: String(badge.id),
424+
attributes: {
425+
'alt-message': badge.altMessage,
426+
'image-url': badge.imageUrl,
427+
'is-always-visible': badge.isAlwaysVisible,
428+
'is-certifiable': badge.isCertifiable,
429+
key: badge.key,
430+
message: badge.message,
431+
title: badge.title,
432+
},
433+
},
434+
{
435+
type: 'competences',
436+
id: learningContentObjects.competences[0].id,
437+
attributes: {
438+
index: learningContentObjects.competences[0].index,
439+
name: learningContentObjects.competences[0].name,
440+
},
441+
},
442+
],
443+
});
444+
});
445+
});
353446
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { usecases } from '../../../../../../src/prescription/campaign/domain/usecases/index.js';
2+
import { LOCALE } from '../../../../../../src/shared/domain/constants.js';
3+
import {
4+
databaseBuilder,
5+
domainBuilder,
6+
expect,
7+
learningContentBuilder,
8+
mockLearningContent,
9+
} from '../../../../../test-helper.js';
10+
11+
const { FRENCH_SPOKEN } = LOCALE;
12+
13+
describe('Integration | Campaign | UseCase | get-presentation-steps', function () {
14+
let user, campaign, badges, competences;
15+
16+
beforeEach(async function () {
17+
const learningContent = domainBuilder.buildLearningContent.withSimpleContent();
18+
const learningContentObjects = learningContentBuilder.fromAreas(learningContent.frameworks[0].areas);
19+
mockLearningContent(learningContentObjects);
20+
21+
const targetProfileId = databaseBuilder.factory.buildTargetProfile().id;
22+
23+
campaign = databaseBuilder.factory.buildCampaign({ targetProfileId });
24+
25+
user = databaseBuilder.factory.buildUser.withMembership({ organizationId: campaign.organizationId });
26+
27+
badges = [
28+
databaseBuilder.factory.buildBadge({ targetProfileId }),
29+
databaseBuilder.factory.buildBadge({ targetProfileId }),
30+
];
31+
32+
competences = learningContentObjects.competences;
33+
34+
databaseBuilder.factory.buildCampaignSkill({
35+
campaignId: campaign.id,
36+
skillId: competences[0].skillIds[0],
37+
});
38+
39+
await databaseBuilder.commit();
40+
});
41+
42+
it('should get campaign presentation steps content', async function () {
43+
// when
44+
const result = await usecases.getPresentationSteps({
45+
userId: user.id,
46+
campaignCode: campaign.code,
47+
locale: FRENCH_SPOKEN,
48+
});
49+
50+
// then
51+
expect(result.customLandingPageText).to.equal(campaign.customLandingPageText);
52+
expect(result.badges).to.deep.equal(badges);
53+
expect(result.competences).to.have.lengthOf(competences.length);
54+
expect(result.competences[0].id).to.equal(competences[0].id);
55+
expect(result.competences[0].index).to.equal(competences[0].index);
56+
expect(result.competences[0].name).to.equal(competences[0].name);
57+
});
58+
});

api/tests/prescription/campaign/unit/application/campaign-controller_test.js

+39
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,43 @@ describe('Unit | Application | Controller | Campaign', function () {
6060
expect(errorCatched).to.be.instanceof(UserNotAuthorizedToAccessEntityError);
6161
});
6262
});
63+
64+
describe('#getPresentationSteps', function () {
65+
it('should return expected results', async function () {
66+
// given
67+
const userId = Symbol('userId');
68+
const campaignCode = Symbol('campaign code');
69+
const locale = FRENCH_SPOKEN;
70+
71+
sinon.stub(usecases, 'getPresentationSteps');
72+
73+
const dependencies = {
74+
extractLocaleFromRequestStub: sinon.stub().returns(locale),
75+
presentationStepsSerializerStub: {
76+
serialize: sinon.stub(),
77+
},
78+
};
79+
80+
const presentationSteps = Symbol('presentation steps');
81+
const expectedResults = Symbol('results');
82+
83+
usecases.getPresentationSteps.withArgs({ userId, campaignCode, locale }).resolves(presentationSteps);
84+
dependencies.presentationStepsSerializerStub.serialize.withArgs(presentationSteps).returns(expectedResults);
85+
86+
const request = {
87+
auth: { credentials: { userId } },
88+
params: { campaignCode },
89+
headers: { 'accept-language': locale },
90+
};
91+
92+
// when
93+
const response = await campaignController.getPresentationSteps(request, hFake, {
94+
extractLocaleFromRequest: dependencies.extractLocaleFromRequestStub,
95+
presentationStepsSerializer: dependencies.presentationStepsSerializerStub,
96+
});
97+
98+
// then
99+
expect(response).to.equal(expectedResults);
100+
});
101+
});
63102
});

0 commit comments

Comments
 (0)