Skip to content

Commit

Permalink
feat(api): add requirement_type compose to enable more flexible quest…
Browse files Browse the repository at this point in the history
… definition
  • Loading branch information
frinyvonnick committed Feb 10, 2025
1 parent e31aa17 commit a55c802
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 23 deletions.
81 changes: 59 additions & 22 deletions api/src/quest/domain/models/Quest.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const COMPARISON = {
ONE_OF: 'one-of',
};

export const COMPOSE_TYPE = 'compose';

class Quest {
constructor({ id, createdAt, updatedAt, rewardType, eligibilityRequirements, successRequirements, rewardId }) {
this.id = id;
Expand All @@ -23,37 +25,72 @@ class Quest {
*/
isCampaignParticipationContributingToQuest({ eligibility, campaignParticipationId }) {
const scopedEligibility = eligibility.buildEligibilityScopedByCampaignParticipationId({ campaignParticipationId });
const isCampaignParticipationType = (requirement) => requirement.type === ELIGIBILITY_TYPES.CAMPAIGN_PARTICIPATIONS;
const isNotCampaignParticipationType = (requirement) =>
requirement.type !== ELIGIBILITY_TYPES.CAMPAIGN_PARTICIPATIONS;
const partitionRequirements = (requirements) => {
return [requirements.filter(isCampaignParticipationType), requirements.filter(isNotCampaignParticipationType)];
};
const [requirementsOfCampaignParticipationType, othersRequirements] = partitionRequirements(

const campaignParticipationRequirements = this.#flattenRequirementsByType(
this.eligibilityRequirements,
ELIGIBILITY_TYPES.CAMPAIGN_PARTICIPATIONS,
);
const otherRequirements = this.#omitRequirementsByType(
this.eligibilityRequirements,
ELIGIBILITY_TYPES.CAMPAIGN_PARTICIPATIONS,
);
let requirementSpecifiqueALaParticipationEstOk = true;
if (requirementsOfCampaignParticipationType.length > 0) {
requirementSpecifiqueALaParticipationEstOk = requirementsOfCampaignParticipationType.some(
(eligibilityRequirement) => this.#checkRequirement(eligibilityRequirement, scopedEligibility),
);

const eligibilityRequirements = otherRequirements;
if (campaignParticipationRequirements.length > 0) {
eligibilityRequirements.push({
type: COMPOSE_TYPE,
data: campaignParticipationRequirements,
comparison: COMPARISON.ONE_OF,
});
}
return this.#isEligible(scopedEligibility, eligibilityRequirements);
}

return (
requirementSpecifiqueALaParticipationEstOk &&
othersRequirements.every((eligibilityRequirement) =>
this.#checkRequirement(eligibilityRequirement, scopedEligibility),
)
);
#omitRequirementsByType(requirements, type) {
let result = [];
for (const requirement of requirements) {
if (requirement.type === COMPOSE_TYPE) {
result = result.push(this.#omitRequirementsByType(requirement.data, type));
} else if (requirement.type !== type) {
result.push(requirement);
}
}
return result;
}

#flattenRequirementsByType(requirements, type) {
let result = [];
const filteredRequierements = requirements.filter((requirement) => [type, COMPOSE_TYPE].includes(requirement.type));
for (const requirement of filteredRequierements) {
if (requirement.type === COMPOSE_TYPE) {
result = result.concat(this.#flattenRequirementsByType(requirement.data, type));
} else {
result.push(requirement);
}
}
return result;
}

#getComparisonFunction(comparison) {
return comparison === COMPARISON.ONE_OF ? 'some' : 'every';
}

#isEligible(eligibility, eligibilityRequirements, comparison = COMPARISON.ALL) {
const comparisonFunction = this.#getComparisonFunction(comparison);
return eligibilityRequirements[comparisonFunction]((eligibilityRequirement) => {
if (eligibilityRequirement.type === COMPOSE_TYPE) {
return this.#isEligible(eligibility, eligibilityRequirement.data, eligibilityRequirement.comparison);
} else {
return this.#checkRequirement(eligibilityRequirement, eligibility);
}
});
}

/**
* @param {Eligibility} eligibility
*/
isEligible(eligibility) {
return this.eligibilityRequirements.every((eligibilityRequirement) =>
this.#checkRequirement(eligibilityRequirement, eligibility),
);
return this.#isEligible(eligibility, this.eligibilityRequirements);
}

#checkCriterion({ criterion, eligibilityData }) {
Expand All @@ -67,7 +104,7 @@ class Quest {
}

#checkRequirement(eligibilityRequirement, eligibility) {
const comparisonFunction = eligibilityRequirement.comparison === COMPARISON.ONE_OF ? 'some' : 'every';
const comparisonFunction = this.#getComparisonFunction(eligibilityRequirement.comparison);

if (Array.isArray(eligibility[eligibilityRequirement.type])) {
return eligibility[eligibilityRequirement.type].some((item) => {
Expand Down
62 changes: 61 additions & 1 deletion api/tests/quest/unit/domain/models/Quest_test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,72 @@
import { Eligibility, TYPES } from '../../../../../src/quest/domain/models/Eligibility.js';
import { Quest } from '../../../../../src/quest/domain/models/Quest.js';
import { COMPOSE_TYPE, Quest } from '../../../../../src/quest/domain/models/Quest.js';
import { COMPARISON } from '../../../../../src/quest/domain/models/Quest.js';
import { Success } from '../../../../../src/quest/domain/models/Success.js';
import { KnowledgeElement } from '../../../../../src/shared/domain/models/index.js';
import { expect } from '../../../../test-helper.js';

describe('Quest | Unit | Domain | Models | Quest ', function () {
describe('#isEligible', function () {
describe('when there are requirements of type "requirements"', function () {
let quest;

beforeEach(function () {
quest = new Quest({
eligibilityRequirements: [
{
type: COMPOSE_TYPE,
comparison: COMPARISON.ONE_OF,
data: [
{
type: COMPOSE_TYPE,
comparison: COMPARISON.ALL,
data: [
{
type: TYPES.CAMPAIGN_PARTICIPATIONS,
data: {
targetProfileId: 1,
},
},
{
type: TYPES.CAMPAIGN_PARTICIPATIONS,
data: {
targetProfileId: 2,
},
},
],
},
{
type: TYPES.CAMPAIGN_PARTICIPATIONS,
data: {
targetProfileId: 3,
},
},
],
},
],
});
});

// eslint-disable-next-line mocha/no-setup-in-describe
[
{
title: 'return false when no requirements passed',
data: [{ targetProfileId: 1 }],
expected: false,
},
{ title: 'return true when one simple requirement passed', data: [{ targetProfileId: 3 }], expected: true },
{
title: 'return true when one nested requirement passed',
data: [{ targetProfileId: 1 }, { targetProfileId: 2 }],
expected: true,
},
].forEach((test) => {
it(test.title, function () {
expect(quest.isEligible(new Eligibility({ campaignParticipations: test.data }))).to.equal(test.expected);
});
});
});

describe('when comparison is "all"', function () {
describe('when data to test is a simple value', function () {
let quest;
Expand Down

0 comments on commit a55c802

Please sign in to comment.