Skip to content

Commit e5f98a0

Browse files
wip
1 parent de2d59f commit e5f98a0

File tree

6 files changed

+375
-5
lines changed

6 files changed

+375
-5
lines changed

api/src/quest/domain/models/Quest.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ class Quest {
5454
);
5555

5656
if (campaignParticipationRequirements.length > 0) {
57-
const a = new ComposedRequirement({
57+
const isParticipationInRequirement = new ComposedRequirement({
5858
data: campaignParticipationRequirements,
5959
comparison: REQUIREMENT_COMPARISONS.ONE_OF,
6060
});
61-
return a.isFulfilled(scopedData);
61+
return isParticipationInRequirement.isFulfilled(scopedData);
6262
}
6363

6464
return false;

api/src/quest/domain/models/Requirement.js

+45
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const COMPARISONS = {
88
export const TYPES = {
99
COMPOSE: 'compose',
1010
SKILL_PROFILE: 'skillProfile',
11+
CAPPED_TUBES_PROFILE: 'cappedTubesProfile',
1112
OBJECT: {
1213
ORGANIZATION_LEARNER: 'organizationLearner',
1314
ORGANIZATION: 'organization',
@@ -167,6 +168,48 @@ export class SkillProfileRequirement extends BaseRequirement {
167168
}
168169
}
169170

171+
export class CappedTubesProfileRequirement extends BaseRequirement {
172+
#cappedTubes;
173+
#threshold;
174+
175+
constructor({ data }) {
176+
super({ requirement_type: TYPES.CAPPED_TUBES_PROFILE, comparison: null });
177+
this.#cappedTubes = data.cappedTubes;
178+
this.#threshold = data.threshold;
179+
}
180+
181+
/**
182+
* @returns {Object}
183+
*/
184+
get data() {
185+
return {
186+
cappedTubes: Object.freeze(this.#cappedTubes),
187+
threshold: this.#threshold,
188+
};
189+
}
190+
191+
/**
192+
* @param {Eligibility|Success} dataInput
193+
* @returns {Boolean}
194+
*/
195+
isFulfilled(dataInput) {
196+
const masteryPercentage = dataInput.getMasteryPercentageForCappedTubes(this.#cappedTubes);
197+
return masteryPercentage >= this.#threshold;
198+
}
199+
200+
/**
201+
* @returns {Object}
202+
*/
203+
toDTO() {
204+
const superDto = super.toDTO();
205+
delete superDto.comparison;
206+
return {
207+
...superDto,
208+
data: this.data,
209+
};
210+
}
211+
}
212+
170213
/**
171214
* @param {Object} params
172215
* @param {string} params.requirement_type
@@ -182,6 +225,8 @@ export function buildRequirement({ requirement_type, data, comparison }) {
182225
return new ObjectRequirement({ requirement_type, data, comparison });
183226
} else if (requirement_type === TYPES.SKILL_PROFILE) {
184227
return new SkillProfileRequirement({ data });
228+
} else if (requirement_type === TYPES.CAPPED_TUBES_PROFILE) {
229+
return new CappedTubesProfileRequirement({ data });
185230
}
186231
throw new Error(`Unknown requirement_type "${requirement_type}"`);
187232
}

api/tests/quest/integration/domain/usecases/reward-user_test.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ const setupContext = async (
3737
status: hasValidatedKnowledgeElements ? VALIDATED : INVALIDATED,
3838
},
3939
];
40-
userKnowledgeElements.map(databaseBuilder.factory.buildKnowledgeElement);
40+
userKnowledgeElements.map((ke) => {
41+
databaseBuilder.factory.buildKnowledgeElement(ke);
42+
databaseBuilder.factory.learningContent.buildSkill({
43+
id: ke.skillId,
44+
tubeId: `tubeFor${ke.skillId}`,
45+
difficulty: 1,
46+
});
47+
});
4148

4249
const organization = databaseBuilder.factory.buildOrganization({ type: userOrganization });
4350
const { id: organizationLearnerId } = databaseBuilder.factory.buildOrganizationLearner({
@@ -128,7 +135,14 @@ describe('Quest | Integration | Domain | Usecases | RewardUser', function () {
128135
status: VALIDATED,
129136
},
130137
];
131-
userKnowledgeElements.map(databaseBuilder.factory.buildKnowledgeElement);
138+
userKnowledgeElements.map((ke) => {
139+
databaseBuilder.factory.buildKnowledgeElement(ke);
140+
databaseBuilder.factory.learningContent.buildSkill({
141+
id: ke.skillId,
142+
tubeId: `tubeFor${ke.skillId}`,
143+
difficulty: 1,
144+
});
145+
});
132146

133147
const organization = databaseBuilder.factory.buildOrganization({ type: questOrganization });
134148
const { id: organizationLearnerId } = databaseBuilder.factory.buildOrganizationLearner({

api/tests/quest/unit/domain/models/Criterion_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('Quest | Unit | Domain | Models | Criterion ', function () {
2121

2222
const result = criterion.check({
2323
item: { something: true, otherthing: false },
24-
comparisonFunction: 'some',
24+
comparisonFunction: 'some', // OUPS
2525
});
2626

2727
expect(result).to.be.true;

api/tests/quest/unit/domain/models/Quest_test.js

+155
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,161 @@ describe('Quest | Unit | Domain | Models | Quest ', function () {
355355
});
356356
});
357357

358+
describe('#findCampaignParticipationIdsContributingToQuest', function () {
359+
const organization = { type: 'SCO' };
360+
const organizationLearner = { id: 123 };
361+
362+
context('at least one eligibilityRequirements is type of "campaignParticipations"', function () {
363+
it('return false if none of campaignParticipation eligibilityRequirement is valid given campaignParticipationId', function () {
364+
// given
365+
const eligibilityRequirements = [
366+
{
367+
requirement_type: REQUIREMENT_TYPES.OBJECT.ORGANIZATION,
368+
data: {
369+
type: {
370+
data: 'SCO',
371+
comparison: CRITERION_COMPARISONS.EQUAL,
372+
},
373+
},
374+
comparison: REQUIREMENT_COMPARISONS.ALL,
375+
},
376+
{
377+
requirement_type: REQUIREMENT_TYPES.OBJECT.CAMPAIGN_PARTICIPATIONS,
378+
data: {
379+
targetProfileId: {
380+
data: 1,
381+
comparison: CRITERION_COMPARISONS.EQUAL,
382+
},
383+
},
384+
comparison: REQUIREMENT_COMPARISONS.ALL,
385+
},
386+
{
387+
requirement_type: REQUIREMENT_TYPES.OBJECT.CAMPAIGN_PARTICIPATIONS,
388+
data: {
389+
targetProfileId: {
390+
data: 2,
391+
comparison: CRITERION_COMPARISONS.EQUAL,
392+
},
393+
},
394+
comparison: REQUIREMENT_COMPARISONS.ALL,
395+
},
396+
];
397+
const quest = new Quest({ eligibilityRequirements, successRequirements: [] });
398+
const campaignParticipations = [
399+
{ id: 10, targetProfileId: 1 },
400+
{ id: 11, targetProfileId: 3 },
401+
];
402+
const eligibility = new Eligibility({ organization, organizationLearner, campaignParticipations });
403+
const data = new DataForQuest({ eligibility });
404+
const campaignParticipationIdToCheck = 11;
405+
406+
// when
407+
const isContributing = quest.isCampaignParticipationContributingToQuest({
408+
data,
409+
campaignParticipationId: campaignParticipationIdToCheck,
410+
});
411+
412+
// then
413+
expect(isContributing).to.be.false;
414+
});
415+
416+
it('return true if one of campaignParticipation eligibilityRequirement is valid given campaignParticipationId', function () {
417+
// given
418+
const eligibilityRequirements = [
419+
{
420+
requirement_type: REQUIREMENT_TYPES.OBJECT.ORGANIZATION,
421+
data: {
422+
type: {
423+
data: 'SCO',
424+
comparison: CRITERION_COMPARISONS.EQUAL,
425+
},
426+
},
427+
comparison: REQUIREMENT_COMPARISONS.ALL,
428+
},
429+
{
430+
requirement_type: REQUIREMENT_TYPES.OBJECT.CAMPAIGN_PARTICIPATIONS,
431+
data: {
432+
targetProfileId: {
433+
data: 1,
434+
comparison: CRITERION_COMPARISONS.EQUAL,
435+
},
436+
},
437+
comparison: REQUIREMENT_COMPARISONS.ALL,
438+
},
439+
{
440+
requirement_type: REQUIREMENT_TYPES.OBJECT.CAMPAIGN_PARTICIPATIONS,
441+
data: {
442+
targetProfileId: {
443+
data: 2,
444+
comparison: CRITERION_COMPARISONS.EQUAL,
445+
},
446+
},
447+
comparison: REQUIREMENT_COMPARISONS.ALL,
448+
},
449+
];
450+
const quest = new Quest({ eligibilityRequirements, successRequirements: [] });
451+
const campaignParticipations = [
452+
{ id: 10, targetProfileId: 1 },
453+
{ id: 11, targetProfileId: 3 },
454+
];
455+
const eligibility = new Eligibility({ organization, organizationLearner, campaignParticipations });
456+
const data = new DataForQuest({ eligibility });
457+
const campaignParticipationIdToCheck = 10;
458+
459+
// when
460+
const isContributing = quest.isCampaignParticipationContributingToQuest({
461+
data,
462+
campaignParticipationId: campaignParticipationIdToCheck,
463+
});
464+
465+
// then
466+
expect(isContributing).to.be.true;
467+
});
468+
});
469+
470+
context('eligibilityRequirement without campaignParticipation type', function () {
471+
it('return false', function () {
472+
// given
473+
const eligibilityRequirements = [
474+
{
475+
requirement_type: REQUIREMENT_TYPES.OBJECT.ORGANIZATION,
476+
data: {
477+
type: {
478+
data: 'SCO',
479+
comparison: CRITERION_COMPARISONS.EQUAL,
480+
},
481+
},
482+
comparison: REQUIREMENT_COMPARISONS.ALL,
483+
},
484+
{
485+
requirement_type: REQUIREMENT_TYPES.OBJECT.ORGANIZATION_LEARNER,
486+
data: {
487+
type: {
488+
id: 123,
489+
comparison: CRITERION_COMPARISONS.EQUAL,
490+
},
491+
},
492+
comparison: REQUIREMENT_COMPARISONS.ALL,
493+
},
494+
];
495+
const quest = new Quest({ eligibilityRequirements, successRequirements: [] });
496+
const campaignParticipations = [{ id: 10 }, { id: 11 }];
497+
const eligibility = new Eligibility({ organization, organizationLearner, campaignParticipations });
498+
const data = new DataForQuest({ eligibility });
499+
const campaignParticipationIdToCheck = 10;
500+
501+
// when
502+
const isContributing = quest.isCampaignParticipationContributingToQuest({
503+
data,
504+
campaignParticipationId: campaignParticipationIdToCheck,
505+
});
506+
507+
// then
508+
expect(isContributing).to.be.false;
509+
});
510+
});
511+
});
512+
358513
describe('#toDTO', function () {
359514
it('should return a DTO version of the Quest', function () {
360515
// given

0 commit comments

Comments
 (0)