diff --git a/api/lib/domain/usecases/index.js b/api/lib/domain/usecases/index.js index f5a59c3dd06..a9b51c89424 100644 --- a/api/lib/domain/usecases/index.js +++ b/api/lib/domain/usecases/index.js @@ -170,7 +170,6 @@ import * as frameworkRepository from '../../infrastructure/repositories/framewor import { repositories } from '../../infrastructure/repositories/index.js'; import { certificationCompletedJobRepository } from '../../infrastructure/repositories/jobs/certification-completed-job-repository.js'; // Not used in lib -import * as learningContentRepository from '../../infrastructure/repositories/learning-content-repository.js'; import * as organizationMemberIdentityRepository from '../../infrastructure/repositories/organization-member-identity-repository.js'; import * as targetProfileShareRepository from '../../infrastructure/repositories/target-profile-share-repository.js'; // Not used in lib @@ -178,7 +177,6 @@ import * as targetProfileTrainingRepository from '../../infrastructure/repositor import * as thematicRepository from '../../infrastructure/repositories/thematic-repository.js'; // Not used in lib import * as stageCollectionRepository from '../../infrastructure/repositories/user-campaign-results/stage-collection-repository.js'; -import * as learningContentConversionService from '../services/learning-content/learning-content-conversion-service.js'; import * as userReconciliationService from '../services/user-reconciliation-service.js'; import * as organizationCreationValidator from '../validators/organization-creation-validator.js'; import * as organizationValidator from '../validators/organization-with-tags-and-target-profiles-script.js'; @@ -275,8 +273,6 @@ const dependencies = { jurySessionRepository, knowledgeElementRepository, languageService, - learningContentConversionService, - learningContentRepository, localeService, mailService, membershipRepository, diff --git a/api/lib/infrastructure/repositories/learning-content-repository.js b/api/lib/infrastructure/repositories/learning-content-repository.js deleted file mode 100644 index f46c90be935..00000000000 --- a/api/lib/infrastructure/repositories/learning-content-repository.js +++ /dev/null @@ -1,144 +0,0 @@ -import _ from 'lodash'; - -import { knex } from '../../../db/knex-database-connection.js'; -import * as campaignRepository from '../../../src/prescription/campaign/infrastructure/repositories/campaign-repository.js'; -import { NoSkillsInCampaignError, NotFoundError } from '../../../src/shared/domain/errors.js'; -import { CampaignLearningContent } from '../../../src/shared/domain/models/CampaignLearningContent.js'; -import { LearningContent } from '../../../src/shared/domain/models/LearningContent.js'; -import * as areaRepository from '../../../src/shared/infrastructure/repositories/area-repository.js'; -import * as competenceRepository from '../../../src/shared/infrastructure/repositories/competence-repository.js'; -import * as skillRepository from '../../../src/shared/infrastructure/repositories/skill-repository.js'; -import * as tubeRepository from '../../../src/shared/infrastructure/repositories/tube-repository.js'; -import * as learningContentConversionService from '../../domain/services/learning-content/learning-content-conversion-service.js'; -import * as frameworkRepository from './framework-repository.js'; -import * as thematicRepository from './thematic-repository.js'; - -async function findByCampaignId(campaignId, locale) { - const skills = await campaignRepository.findSkills({ campaignId }); - - const frameworks = await _getLearningContentBySkillIds(skills, locale); - - return new CampaignLearningContent(frameworks); -} - -async function findByTargetProfileId(targetProfileId, locale) { - const cappedTubesDTO = await knex('target-profile_tubes') - .select({ - id: 'tubeId', - level: 'level', - }) - .where({ targetProfileId }); - - if (cappedTubesDTO.length === 0) { - throw new NotFoundError("Le profil cible n'existe pas"); - } - - const frameworks = await _getLearningContentByCappedTubes(cappedTubesDTO, locale); - return new LearningContent(frameworks); -} - -async function findByFrameworkNames({ frameworkNames, locale }) { - const baseFrameworks = []; - for (const frameworkName of frameworkNames) { - baseFrameworks.push(await frameworkRepository.getByName(frameworkName)); - } - - const frameworks = await _getLearningContentByFrameworks(baseFrameworks, locale); - return new LearningContent(frameworks); -} - -async function _getLearningContentBySkillIds(skills, locale) { - if (_.isEmpty(skills)) { - throw new NoSkillsInCampaignError(); - } - const tubeIds = _.uniq(skills.map((skill) => skill.tubeId)); - const tubes = await tubeRepository.findByRecordIds(tubeIds, locale); - - tubes.forEach((tube) => { - tube.skills = skills.filter((skill) => { - return skill.tubeId === tube.id; - }); - }); - - return _getLearningContentByTubes(tubes, locale); -} - -async function _getLearningContentByCappedTubes(cappedTubesDTO, locale) { - const skills = await learningContentConversionService.findActiveSkillsForCappedTubes(cappedTubesDTO); - - const tubes = await tubeRepository.findByRecordIds( - cappedTubesDTO.map((dto) => dto.id), - locale, - ); - - tubes.forEach((tube) => { - tube.skills = skills.filter((skill) => { - return skill.tubeId === tube.id; - }); - }); - - return _getLearningContentByTubes(tubes, locale); -} - -async function _getLearningContentByTubes(tubes, locale) { - const thematicIds = _.uniq(tubes.map((tube) => tube.thematicId)); - const thematics = await thematicRepository.findByRecordIds(thematicIds, locale); - thematics.forEach((thematic) => { - thematic.tubes = tubes.filter((tube) => tube.thematicId === thematic.id); - }); - - const competenceIds = _.uniq(tubes.map((tube) => tube.competenceId)); - const competences = await competenceRepository.findByRecordIds({ competenceIds, locale }); - - competences.forEach((competence) => { - competence.tubes = tubes.filter((tube) => { - return tube.competenceId === competence.id; - }); - competence.thematics = thematics.filter((thematic) => { - return thematic.competenceId === competence.id; - }); - }); - - const allAreaIds = _.map(competences, (competence) => competence.areaId); - const uniqAreaIds = _.uniq(allAreaIds, 'id'); - const areas = await areaRepository.findByRecordIds({ areaIds: uniqAreaIds, locale }); - for (const area of areas) { - area.competences = competences.filter((competence) => { - return competence.areaId === area.id; - }); - } - - const frameworkIds = _.uniq(areas.map((area) => area.frameworkId)); - const frameworks = await frameworkRepository.findByRecordIds(frameworkIds); - for (const framework of frameworks) { - framework.areas = areas.filter((area) => { - return area.frameworkId === framework.id; - }); - } - - return frameworks; -} - -async function _getLearningContentByFrameworks(frameworks, locale) { - for (const framework of frameworks) { - framework.areas = await areaRepository.findByFrameworkId({ frameworkId: framework.id, locale }); - for (const area of framework.areas) { - area.competences = await competenceRepository.findByAreaId({ areaId: area.id, locale }); - for (const competence of area.competences) { - competence.thematics = await thematicRepository.findByCompetenceIds([competence.id], locale); - for (const thematic of competence.thematics) { - const tubes = await tubeRepository.findActiveByRecordIds(thematic.tubeIds, locale); - thematic.tubes = tubes; - competence.tubes.push(...tubes); - for (const tube of thematic.tubes) { - tube.skills = await skillRepository.findActiveByTubeId(tube.id); - } - } - } - } - } - - return frameworks; -} - -export { findByCampaignId, findByFrameworkNames, findByTargetProfileId }; diff --git a/api/src/certification/evaluation/domain/services/certification-challenges-service.js b/api/src/certification/evaluation/domain/services/certification-challenges-service.js index ee313e786d6..83f273d8ebf 100644 --- a/api/src/certification/evaluation/domain/services/certification-challenges-service.js +++ b/api/src/certification/evaluation/domain/services/certification-challenges-service.js @@ -1,6 +1,6 @@ import _ from 'lodash'; -import * as learningContentRepository from '../../../../../lib/infrastructure/repositories/learning-content-repository.js'; +import * as learningContentRepository from '../../../../learning-content/infrastructure/repositories/learning-content-repository.js'; import { MAX_CHALLENGES_PER_AREA_FOR_CERTIFICATION_PLUS, MAX_CHALLENGES_PER_COMPETENCE_FOR_CERTIFICATION, diff --git a/api/lib/domain/services/learning-content/learning-content-conversion-service.js b/api/src/learning-content/domain/services/learning-content-conversion-service.js similarity index 100% rename from api/lib/domain/services/learning-content/learning-content-conversion-service.js rename to api/src/learning-content/domain/services/learning-content-conversion-service.js diff --git a/api/src/learning-content/infrastructure/repositories/learning-content-repository.js b/api/src/learning-content/infrastructure/repositories/learning-content-repository.js index 00ea1ee02bb..9084549283b 100644 --- a/api/src/learning-content/infrastructure/repositories/learning-content-repository.js +++ b/api/src/learning-content/infrastructure/repositories/learning-content-repository.js @@ -1,63 +1,144 @@ -import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js'; -import { child, SCOPES } from '../../../shared/infrastructure/utils/logger.js'; - -const logger = child('learningcontent:repository', { event: SCOPES.LEARNING_CONTENT }); - -export class LearningContentRepository { - /** @type {string} */ - #tableName; - - /** @type {number} */ - #chunkSize; - - /** - * @param {{ - * tableName: string - * chunkSize?: number - * }} config - */ - constructor({ tableName, chunkSize = 1000 }) { - this.#tableName = tableName; - this.#chunkSize = chunkSize; +import _ from 'lodash'; + +import { knex } from '../../../../db/knex-database-connection.js'; +import * as campaignRepository from '../../../prescription/campaign/infrastructure/repositories/campaign-repository.js'; +import { NoSkillsInCampaignError, NotFoundError } from '../../../shared/domain/errors.js'; +import { CampaignLearningContent } from '../../../shared/domain/models/CampaignLearningContent.js'; +import { LearningContent } from '../../../shared/domain/models/LearningContent.js'; +import * as areaRepository from '../../../shared/infrastructure/repositories/area-repository.js'; +import * as competenceRepository from '../../../shared/infrastructure/repositories/competence-repository.js'; +import * as skillRepository from '../../../shared/infrastructure/repositories/skill-repository.js'; +import * as tubeRepository from '../../../shared/infrastructure/repositories/tube-repository.js'; +import * as learningContentConversionService from '../../domain/services/learning-content-conversion-service.js'; +import * as frameworkRepository from './framework-repository.js'; +import * as thematicRepository from './thematic-repository.js'; + +async function findByCampaignId(campaignId, locale) { + const skills = await campaignRepository.findSkills({ campaignId }); + + const frameworks = await _getLearningContentBySkillIds(skills, locale); + + return new CampaignLearningContent(frameworks); +} + +async function findByTargetProfileId(targetProfileId, locale) { + const cappedTubesDTO = await knex('target-profile_tubes') + .select({ + id: 'tubeId', + level: 'level', + }) + .where({ targetProfileId }); + + if (cappedTubesDTO.length === 0) { + throw new NotFoundError("Le profil cible n'existe pas"); } - /** - * @param {object[]} objects - */ - async saveMany(objects) { - if (!objects) return; - logger.debug(`saving ${objects.length} items in ${this.#tableName}`); - const dtos = objects.map(this.toDto); - const knex = DomainTransaction.getConnection(); - for (const chunk of chunks(dtos, this.#chunkSize)) { - await knex.insert(chunk).into(this.#tableName).onConflict('id').merge(); - } + const frameworks = await _getLearningContentByCappedTubes(cappedTubesDTO, locale); + return new LearningContent(frameworks); +} + +async function findByFrameworkNames({ frameworkNames, locale }) { + const baseFrameworks = []; + for (const frameworkName of frameworkNames) { + baseFrameworks.push(await frameworkRepository.getByName(frameworkName)); } - /** - * @param {object} object - */ - async save(object) { - logger.debug(`saving one item in ${this.#tableName}`); - const dto = this.toDto(object); - const knex = DomainTransaction.getConnection(); - await knex.insert(dto).into(this.#tableName).onConflict('id').merge(); + const frameworks = await _getLearningContentByFrameworks(baseFrameworks, locale); + return new LearningContent(frameworks); +} + +async function _getLearningContentBySkillIds(skills, locale) { + if (_.isEmpty(skills)) { + throw new NoSkillsInCampaignError(); } + const tubeIds = _.uniq(skills.map((skill) => skill.tubeId)); + const tubes = await tubeRepository.findByRecordIds(tubeIds, locale); + + tubes.forEach((tube) => { + tube.skills = skills.filter((skill) => { + return skill.tubeId === tube.id; + }); + }); + + return _getLearningContentByTubes(tubes, locale); +} - /** - * Maps an object to a DTO before insertion. - * @param {object} _object - * @returns {object} - */ - toDto(_object) { - // must be overridden +async function _getLearningContentByCappedTubes(cappedTubesDTO, locale) { + const skills = await learningContentConversionService.findActiveSkillsForCappedTubes(cappedTubesDTO); + + const tubes = await tubeRepository.findByRecordIds( + cappedTubesDTO.map((dto) => dto.id), + locale, + ); + + tubes.forEach((tube) => { + tube.skills = skills.filter((skill) => { + return skill.tubeId === tube.id; + }); + }); + + return _getLearningContentByTubes(tubes, locale); +} + +async function _getLearningContentByTubes(tubes, locale) { + const thematicIds = _.uniq(tubes.map((tube) => tube.thematicId)); + const thematics = await thematicRepository.findByRecordIds(thematicIds, locale); + thematics.forEach((thematic) => { + thematic.tubes = tubes.filter((tube) => tube.thematicId === thematic.id); + }); + + const competenceIds = _.uniq(tubes.map((tube) => tube.competenceId)); + const competences = await competenceRepository.findByRecordIds({ competenceIds, locale }); + + competences.forEach((competence) => { + competence.tubes = tubes.filter((tube) => { + return tube.competenceId === competence.id; + }); + competence.thematics = thematics.filter((thematic) => { + return thematic.competenceId === competence.id; + }); + }); + + const allAreaIds = _.map(competences, (competence) => competence.areaId); + const uniqAreaIds = _.uniq(allAreaIds, 'id'); + const areas = await areaRepository.findByRecordIds({ areaIds: uniqAreaIds, locale }); + for (const area of areas) { + area.competences = competences.filter((competence) => { + return competence.areaId === area.id; + }); + } + + const frameworkIds = _.uniq(areas.map((area) => area.frameworkId)); + const frameworks = await frameworkRepository.findByRecordIds(frameworkIds); + for (const framework of frameworks) { + framework.areas = areas.filter((area) => { + return area.frameworkId === framework.id; + }); } + + return frameworks; } -function chunks(items, size) { - const chunks = []; - for (let i = 0; i < items.length; i += size) { - chunks.push(items.slice(i, i + size)); +async function _getLearningContentByFrameworks(frameworks, locale) { + for (const framework of frameworks) { + framework.areas = await areaRepository.findByFrameworkId({ frameworkId: framework.id, locale }); + for (const area of framework.areas) { + area.competences = await competenceRepository.findByAreaId({ areaId: area.id, locale }); + for (const competence of area.competences) { + competence.thematics = await thematicRepository.findByCompetenceIds([competence.id], locale); + for (const thematic of competence.thematics) { + const tubes = await tubeRepository.findActiveByRecordIds(thematic.tubeIds, locale); + thematic.tubes = tubes; + competence.tubes.push(...tubes); + for (const tube of thematic.tubes) { + tube.skills = await skillRepository.findActiveByTubeId(tube.id); + } + } + } + } } - return chunks; + + return frameworks; } + +export { findByCampaignId, findByFrameworkNames, findByTargetProfileId }; diff --git a/api/src/prescription/campaign-participation/domain/usecases/index.js b/api/src/prescription/campaign-participation/domain/usecases/index.js index a6e47740e44..5a2f9722abb 100644 --- a/api/src/prescription/campaign-participation/domain/usecases/index.js +++ b/api/src/prescription/campaign-participation/domain/usecases/index.js @@ -3,7 +3,6 @@ import { fileURLToPath } from 'node:url'; import * as badgeAcquisitionRepository from '../../../../../lib/infrastructure/repositories/badge-acquisition-repository.js'; import * as badgeForCalculationRepository from '../../../../../lib/infrastructure/repositories/badge-for-calculation-repository.js'; -import * as learningContentRepository from '../../../../../lib/infrastructure/repositories/learning-content-repository.js'; import * as stageCollectionRepository from '../../../../../lib/infrastructure/repositories/user-campaign-results/stage-collection-repository.js'; import * as tutorialRepository from '../../../../devcomp/infrastructure/repositories/tutorial-repository.js'; import * as compareStagesAndAcquiredStages from '../../../../evaluation/domain/services/stages/stage-and-stage-acquisition-comparison-service.js'; @@ -13,6 +12,7 @@ import * as stageAcquisitionRepository from '../../../../evaluation/infrastructu import * as stageRepository from '../../../../evaluation/infrastructure/repositories/stage-repository.js'; import * as authenticationMethodRepository from '../../../../identity-access-management/infrastructure/repositories/authentication-method.repository.js'; import * as userRepository from '../../../../identity-access-management/infrastructure/repositories/user.repository.js'; +import * as learningContentRepository from '../../../../learning-content/infrastructure/repositories/learning-content-repository.js'; import { config } from '../../../../shared/config.js'; import * as areaRepository from '../../../../shared/infrastructure/repositories/area-repository.js'; import * as assessmentRepository from '../../../../shared/infrastructure/repositories/assessment-repository.js'; diff --git a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-result-repository.js b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-result-repository.js index ea4c1581657..b121ef5fffb 100644 --- a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-result-repository.js +++ b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-result-repository.js @@ -1,5 +1,5 @@ import { knex } from '../../../../../db/knex-database-connection.js'; -import * as learningContentRepository from '../../../../../lib/infrastructure/repositories/learning-content-repository.js'; +import * as learningContentRepository from '../../../../learning-content/infrastructure/repositories/learning-content-repository.js'; import { NotFoundError } from '../../../../shared/domain/errors.js'; import * as knowledgeElementRepository from '../../../../shared/infrastructure/repositories/knowledge-element-repository.js'; import { CampaignAssessmentParticipationResult } from '../../domain/models/CampaignAssessmentParticipationResult.js'; diff --git a/api/src/prescription/campaign/domain/usecases/index.js b/api/src/prescription/campaign/domain/usecases/index.js index 2c89efe9069..17ba5bbf783 100644 --- a/api/src/prescription/campaign/domain/usecases/index.js +++ b/api/src/prescription/campaign/domain/usecases/index.js @@ -2,12 +2,11 @@ import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import * as badgeAcquisitionRepository from '../../../../../lib/infrastructure/repositories/badge-acquisition-repository.js'; -import * as learningContentRepository from '../../../../../lib/infrastructure/repositories/learning-content-repository.js'; import * as stageCollectionRepository from '../../../../../lib/infrastructure/repositories/user-campaign-results/stage-collection-repository.js'; -import * as campaignRepository from '../../../../../src/prescription/campaign/infrastructure/repositories/campaign-repository.js'; import * as tutorialRepository from '../../../../devcomp/infrastructure/repositories/tutorial-repository.js'; import * as badgeRepository from '../../../../evaluation/infrastructure/repositories/badge-repository.js'; import * as userRepository from '../../../../identity-access-management/infrastructure/repositories/user.repository.js'; +import * as learningContentRepository from '../../../../learning-content/infrastructure/repositories/learning-content-repository.js'; import * as organizationFeatureApi from '../../../../organizational-entities/application/api/organization-features-api.js'; import * as codeGenerator from '../../../../shared/domain/services/code-generator.js'; import * as placementProfileService from '../../../../shared/domain/services/placement-profile-service.js'; @@ -17,6 +16,7 @@ import * as organizationRepository from '../../../../shared/infrastructure/repos import { injectDependencies } from '../../../../shared/infrastructure/utils/dependency-injection.js'; import { importNamedExportsFromDirectory } from '../../../../shared/infrastructure/utils/import-named-exports-from-directory.js'; import * as membershipRepository from '../../../../team/infrastructure/repositories/membership.repository.js'; +import * as campaignRepository from '../../../campaign/infrastructure/repositories/campaign-repository.js'; import * as campaignAnalysisRepository from '../../../campaign-participation/infrastructure/repositories/campaign-analysis-repository.js'; import * as campaignParticipationRepository from '../../../campaign-participation/infrastructure/repositories/campaign-participation-repository.js'; import * as organizationLearnerImportFormatRepository from '../../../learner-management/infrastructure/repositories/organization-learner-import-format-repository.js'; diff --git a/api/src/prescription/target-profile/domain/usecases/index.js b/api/src/prescription/target-profile/domain/usecases/index.js index e5f4befd719..781e321319f 100644 --- a/api/src/prescription/target-profile/domain/usecases/index.js +++ b/api/src/prescription/target-profile/domain/usecases/index.js @@ -1,8 +1,8 @@ import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; -import * as learningContentConversionService from '../../../../../lib/domain/services/learning-content/learning-content-conversion-service.js'; -import * as learningContentRepository from '../../../../../lib/infrastructure/repositories/learning-content-repository.js'; +import * as learningContentConversionService from '../../../../learning-content/domain/services/learning-content-conversion-service.js'; +import * as learningContentRepository from '../../../../learning-content/infrastructure/repositories/learning-content-repository.js'; import { adminMemberRepository } from '../../../../shared/infrastructure/repositories/admin-member.repository.js'; import * as organizationRepository from '../../../../shared/infrastructure/repositories/organization-repository.js'; import { injectDependencies } from '../../../../shared/infrastructure/utils/dependency-injection.js'; diff --git a/api/tests/integration/infrastructure/repositories/learning-content-repository_test.js b/api/tests/integration/infrastructure/repositories/learning-content-repository_test.js deleted file mode 100644 index 068a8f15bf4..00000000000 --- a/api/tests/integration/infrastructure/repositories/learning-content-repository_test.js +++ /dev/null @@ -1,486 +0,0 @@ -import * as learningContentRepository from '../../../../lib/infrastructure/repositories/learning-content-repository.js'; -import { NoSkillsInCampaignError, NotFoundError } from '../../../../src/shared/domain/errors.js'; -import { catchErr, databaseBuilder, domainBuilder, expect } from '../../../test-helper.js'; - -describe('Integration | Repository | learning-content', function () { - let framework1Fr, framework1En, framework2Fr, framework2En; - let area1Fr, area1En, area2Fr, area2En; - let competence1Fr, competence1En, competence2Fr, competence2En, competence3Fr, competence3En; - let thematic1Fr, thematic1En, thematic2Fr, thematic2En, thematic3Fr, thematic3En; - let tube1Fr, tube1En, tube2Fr, tube2En, tube4Fr, tube4En; - let skill1Fr, skill2Fr, skill3Fr, skill8Fr; - - beforeEach(async function () { - const framework1DB = databaseBuilder.factory.learningContent.buildFramework({ - id: 'recFramework1', - name: 'Mon référentiel 1', - }); - const framework2DB = databaseBuilder.factory.learningContent.buildFramework({ - id: 'recFramework2', - name: 'Mon référentiel 2', - }); - const area1DB = databaseBuilder.factory.learningContent.buildArea({ - id: 'recArea1', - name: 'area1_name', - title_i18n: { fr: 'domaine1_TitreFr', en: 'area1_TitleEn' }, - color: 'area1_color', - code: 'area1_code', - frameworkId: 'recFramework1', - competenceIds: ['recCompetence1', 'recCompetence2'], - }); - const area2DB = databaseBuilder.factory.learningContent.buildArea({ - id: 'recArea2', - name: 'area2_name', - title_i18n: { fr: 'domaine2_TitreFr', en: 'area2_TitleEn' }, - color: 'area2_color', - code: 'area2_code', - frameworkId: 'recFramework2', - competenceIds: ['recCompetence3'], - }); - const competence1DB = databaseBuilder.factory.learningContent.buildCompetence({ - id: 'recCompetence1', - name_i18n: { fr: 'competence1_nomFr', en: 'competence1_nameEn' }, - index: '1', - description_i18n: { fr: 'competence1_descriptionFr', en: 'competence1_descriptionEn' }, - origin: 'Pix', - areaId: 'recArea1', - }); - const competence2DB = databaseBuilder.factory.learningContent.buildCompetence({ - id: 'recCompetence2', - name_i18n: { fr: 'competence2_nomFr', en: 'competence2_nameEn' }, - index: '2', - description_i18n: { fr: 'competence2_descriptionFr', en: 'competence2_descriptionEn' }, - origin: 'Pix', - areaId: 'recArea1', - }); - const competence3DB = databaseBuilder.factory.learningContent.buildCompetence({ - id: 'recCompetence3', - name_i18n: { fr: 'competence3_nomFr', en: 'competence3_nameEn' }, - index: '1', - description_i18n: { fr: 'competence3_descriptionFr', en: 'competence3_descriptionEn' }, - origin: 'Pix', - areaId: 'recArea2', - }); - const thematic1DB = databaseBuilder.factory.learningContent.buildThematic({ - id: 'recThematic1', - name_i18n: { - fr: 'thematique1_nomFr', - en: 'thematic1_nameEn', - }, - index: 10, - competenceId: 'recCompetence1', - tubeIds: ['recTube1'], - }); - const thematic2DB = databaseBuilder.factory.learningContent.buildThematic({ - id: 'recThematic2', - name_i18n: { - fr: 'thematique2_nomFr', - en: 'thematic2_nameEn', - }, - index: 20, - competenceId: 'recCompetence2', - tubeIds: ['recTube2', 'recTube3'], - }); - const thematic3DB = databaseBuilder.factory.learningContent.buildThematic({ - id: 'recThematic3', - name_i18n: { - fr: 'thematique3_nomFr', - en: 'thematic3_nameEn', - }, - index: 30, - competenceId: 'recCompetence3', - tubeIds: ['recTube4'], - }); - const tube1DB = databaseBuilder.factory.learningContent.buildTube({ - id: 'recTube1', - name: '@tube1_name', - title: 'tube1_title', - description: 'tube1_description', - practicalTitle_i18n: { fr: 'tube1_practicalTitleFr', en: 'tube1_practicalTitleEn' }, - practicalDescription_i18n: { - fr: 'tube1_practicalDescriptionFr', - en: 'tube1_practicalDescriptionEn', - }, - isMobileCompliant: true, - isTabletCompliant: false, - competenceId: 'recCompetence1', - thematicId: 'recThematic1', - }); - const tube2DB = databaseBuilder.factory.learningContent.buildTube({ - id: 'recTube2', - name: '@tube2_name', - title: '@tube2_title', - description: '@tube2_description', - practicalTitle_i18n: { fr: 'tube2_practicalTitleFr', en: 'tube2_practicalTitleEn' }, - practicalDescription_i18n: { - fr: 'tube2_practicalDescriptionFr', - en: 'tube2_practicalDescriptionEn', - }, - isMobileCompliant: false, - isTabletCompliant: true, - competenceId: 'recCompetence2', - thematicId: 'recThematic2', - }); - const tube3DB = databaseBuilder.factory.learningContent.buildTube({ - id: 'recTube3', - name: '@tube3_name', - title: '@tube3_title', - description: '@tube3_description', - practicalTitle_i18n: { fr: 'tube3_practicalTitleFr', en: 'tube3_practicalTitleEn' }, - practicalDescription_i18n: { - fr: 'tube3_practicalDescriptionFr', - en: 'tube3_practicalDescriptionEn', - }, - isMobileCompliant: true, - isTabletCompliant: true, - competenceId: 'recCompetence2', - thematicId: 'recThematic2', - }); - const tube4DB = databaseBuilder.factory.learningContent.buildTube({ - id: 'recTube4', - name: '@tube4_name', - title: 'tube4_title', - description: 'tube4_description', - practicalTitle_i18n: { fr: 'tube4_practicalTitleFr', en: 'tube4_practicalTitleEn' }, - practicalDescription_i18n: { - fr: 'tube4_practicalDescriptionFr', - en: 'tube4_practicalDescriptionEn', - }, - isMobileCompliant: false, - isTabletCompliant: false, - competenceId: 'recCompetence3', - thematicId: 'recThematic3', - }); - const skill1DB = databaseBuilder.factory.learningContent.buildSkill({ - id: 'recSkill1', - name: '@tube1_name4', - status: 'actif', - level: 4, - pixValue: 12, - version: 98, - tubeId: 'recTube1', - }); - const skill2DB = databaseBuilder.factory.learningContent.buildSkill({ - id: 'recSkill2', - name: '@tube2_name1', - status: 'actif', - level: 1, - pixValue: 34, - version: 76, - tubeId: 'recTube2', - }); - const skill3DB = databaseBuilder.factory.learningContent.buildSkill({ - id: 'recSkill3', - name: '@tube2_name2', - status: 'archivé', - level: 2, - pixValue: 56, - version: 54, - tubeId: 'recTube2', - }); - const skill4DB = databaseBuilder.factory.learningContent.buildSkill({ - id: 'recSkill4', - status: 'périmé', - tubeId: 'recTube2', - }); - const skill5DB = databaseBuilder.factory.learningContent.buildSkill({ - id: 'recSkill5', - name: '@tube3_name5', - status: 'archivé', - level: 5, - pixValue: 44, - version: 55, - tubeId: 'recTube3', - }); - const skill6DB = databaseBuilder.factory.learningContent.buildSkill({ - id: 'recSkill6', - status: 'périmé', - tubeId: 'recTube3', - }); - const skill7DB = databaseBuilder.factory.learningContent.buildSkill({ - id: 'recSkill7', - status: 'périmé', - tubeId: 'recTube3', - }); - const skill8DB = databaseBuilder.factory.learningContent.buildSkill({ - id: 'recSkill8', - name: '@tube4_name8', - status: 'actif', - level: 7, - pixValue: 78, - version: 32, - tubeId: 'recTube4', - }); - await databaseBuilder.commit(); - - [framework1Fr, framework2Fr] = _buildDomainFrameworksFromDB([framework1DB, framework2DB]); - [framework1En, framework2En] = _buildDomainFrameworksFromDB([framework1DB, framework2DB]); - [area1Fr, area2Fr] = _buildDomainAreasFromDB([area1DB, area2DB], 'fr'); - [area1En, area2En] = _buildDomainAreasFromDB([area1DB, area2DB], 'en'); - [competence1Fr, competence2Fr, competence3Fr] = _buildDomainCompetencesFromDB( - [competence1DB, competence2DB, competence3DB], - 'fr', - ); - [competence1En, competence2En, competence3En] = _buildDomainCompetencesFromDB( - [competence1DB, competence2DB, competence3DB], - 'en', - ); - [thematic1Fr, thematic2Fr, thematic3Fr] = _buildDomainThematicsFromDB( - [thematic1DB, thematic2DB, thematic3DB], - 'fr', - ); - [thematic1En, thematic2En, thematic3En] = _buildDomainThematicsFromDB( - [thematic1DB, thematic2DB, thematic3DB], - 'en', - ); - [tube1Fr, tube2Fr, , tube4Fr] = _buildDomainTubesFromDB([tube1DB, tube2DB, tube3DB, tube4DB], 'fr'); - [tube1En, tube2En, , tube4En] = _buildDomainTubesFromDB([tube1DB, tube2DB, tube3DB, tube4DB], 'en'); - [skill1Fr, skill2Fr, skill3Fr, , , , , skill8Fr] = _buildDomainSkillsFromDB( - [skill1DB, skill2DB, skill3DB, skill4DB, skill5DB, skill6DB, skill7DB, skill8DB], - 'fr', - ); - }); - - describe('#findByCampaignId', function () { - let campaignId; - - it('should return frameworks, areas, competences, thematics and tubes of the skills hierarchy', async function () { - // given - campaignId = databaseBuilder.factory.buildCampaign().id; - ['recSkill2', 'recSkill3'].forEach((skillId) => - databaseBuilder.factory.buildCampaignSkill({ campaignId, skillId }), - ); - await databaseBuilder.commit(); - - framework1Fr.areas = [area1Fr]; - area1Fr.competences = [competence2Fr]; - competence2Fr.thematics = [thematic2Fr]; - competence2Fr.tubes = [tube2Fr]; - thematic2Fr.tubes = [tube2Fr]; - tube2Fr.skills = [skill2Fr, skill3Fr]; - - // when - const learningContentFromCampaign = await learningContentRepository.findByCampaignId(campaignId); - - // then - expect(learningContentFromCampaign.areas).to.deep.equal([area1Fr]); - expect(learningContentFromCampaign.frameworks).to.deep.equal([framework1Fr]); - }); - - it('should translate names and descriptions when specifying a locale', async function () { - // given - campaignId = databaseBuilder.factory.buildCampaign().id; - ['recSkill2', 'recSkill3'].forEach((skillId) => - databaseBuilder.factory.buildCampaignSkill({ campaignId, skillId }), - ); - await databaseBuilder.commit(); - - framework1En.areas = [area1En]; - area1En.competences = [competence2En]; - competence2En.thematics = [thematic2En]; - competence2En.tubes = [tube2En]; - thematic2En.tubes = [tube2En]; - tube2En.skills = [skill2Fr, skill3Fr]; - - // when - const learningContentFromCampaign = await learningContentRepository.findByCampaignId(campaignId, 'en'); - - // then - expect(learningContentFromCampaign.frameworks).to.deep.equal([framework1En]); - }); - - it('should throw a NoSkillsInCampaignError when there are no more operative skills', async function () { - // given - campaignId = databaseBuilder.factory.buildCampaign().id; - databaseBuilder.factory.buildCampaignSkill({ campaignId, skillId: 'recSkill4' }); - await databaseBuilder.commit(); - - // when - const err = await catchErr(learningContentRepository.findByCampaignId)(campaignId); - - // then - expect(err).to.be.instanceOf(NoSkillsInCampaignError); - }); - }); - - describe('#findByTargetProfileId', function () { - context('when target profile does not have capped tubes', function () { - it('should throw a NotFound error', async function () { - // given - const targetProfileId = databaseBuilder.factory.buildTargetProfile().id; - const anotherTargetProfileId = databaseBuilder.factory.buildTargetProfile().id; - databaseBuilder.factory.buildTargetProfileTube({ targetProfileId: anotherTargetProfileId }); - await databaseBuilder.commit(); - - // when - const error = await catchErr(learningContentRepository.findByTargetProfileId)(targetProfileId); - - // then - expect(error).to.be.instanceOf(NotFoundError); - expect(error.message).to.equal("Le profil cible n'existe pas"); - }); - }); - - context('when target profile has capped tubes', function () { - it('should return frameworks, areas, competences, thematics and tubes of the active skills hierarchy with default FR language', async function () { - // given - const targetProfileId = databaseBuilder.factory.buildTargetProfile().id; - databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'recTube2', level: 2 }); - await databaseBuilder.commit(); - - framework1Fr.areas = [area1Fr]; - area1Fr.competences = [competence2Fr]; - competence2Fr.thematics = [thematic2Fr]; - competence2Fr.tubes = [tube2Fr]; - thematic2Fr.tubes = [tube2Fr]; - tube2Fr.skills = [skill2Fr]; - - // when - const targetProfileLearningContent = await learningContentRepository.findByTargetProfileId(targetProfileId); - - // then - expect(targetProfileLearningContent.frameworks).to.deep.equal([framework1Fr]); - }); - - context('when using a specific locale', function () { - it('should translate names and descriptions', async function () { - // given - const targetProfileId = databaseBuilder.factory.buildTargetProfile().id; - databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'recTube2', level: 2 }); - await databaseBuilder.commit(); - - framework1En.areas = [area1En]; - area1En.competences = [competence2En]; - competence2En.thematics = [thematic2En]; - competence2En.tubes = [tube2En]; - thematic2En.tubes = [tube2En]; - tube2En.skills = [skill2Fr]; - - // when - const targetProfileLearningContent = await learningContentRepository.findByTargetProfileId( - targetProfileId, - 'en', - ); - - // then - expect(targetProfileLearningContent.frameworks).to.deep.equal([framework1En]); - }); - }); - }); - }); - - describe('#findByFrameworkNames', function () { - it('should return an active LearningContent with the frameworks designated by name', async function () { - // given - framework1Fr.areas = [area1Fr]; - framework2Fr.areas = [area2Fr]; - area1Fr.competences = [competence1Fr, competence2Fr]; - area2Fr.competences = [competence3Fr]; - competence1Fr.thematics = [thematic1Fr]; - competence1Fr.tubes = [tube1Fr]; - competence2Fr.thematics = [thematic2Fr]; - competence2Fr.tubes = [tube2Fr]; - competence3Fr.thematics = [thematic3Fr]; - competence3Fr.tubes = [tube4Fr]; - thematic1Fr.tubes = [tube1Fr]; - thematic2Fr.tubes = [tube2Fr]; - thematic3Fr.tubes = [tube4Fr]; - tube1Fr.skills = [skill1Fr]; - tube2Fr.skills = [skill2Fr]; - tube4Fr.skills = [skill8Fr]; - - // when - const learningContent = await learningContentRepository.findByFrameworkNames({ - frameworkNames: ['Mon référentiel 1', 'Mon référentiel 2'], - }); - - // then - const expectedLearningContent = domainBuilder.buildLearningContent([framework1Fr, framework2Fr]); - expect(learningContent).to.deepEqualInstance(expectedLearningContent); - }); - - it('should return an active LearningContent in the given language', async function () { - // given - framework1En.areas = [area1En]; - framework2En.areas = [area2En]; - area1En.competences = [competence1En, competence2En]; - area2En.competences = [competence3En]; - competence1En.thematics = [thematic1En]; - competence1En.tubes = [tube1En]; - competence2En.thematics = [thematic2En]; - competence2En.tubes = [tube2En]; - competence3En.thematics = [thematic3En]; - competence3En.tubes = [tube4En]; - thematic1En.tubes = [tube1En]; - thematic2En.tubes = [tube2En]; - thematic3En.tubes = [tube4En]; - tube1En.skills = [skill1Fr]; - tube2En.skills = [skill2Fr]; - tube4En.skills = [skill8Fr]; - - // when - const learningContent = await learningContentRepository.findByFrameworkNames({ - frameworkNames: ['Mon référentiel 1', 'Mon référentiel 2'], - locale: 'en', - }); - - // then - const expectedLearningContent = domainBuilder.buildLearningContent([framework1En, framework2En]); - expect(learningContent).to.deepEqualInstance(expectedLearningContent); - }); - }); -}); - -function _buildDomainFrameworksFromDB(frameworksDB) { - return frameworksDB.map((frameworkDB) => - domainBuilder.buildFramework({ - id: frameworkDB.id, - name: frameworkDB.name, - areas: [], - }), - ); -} - -function _buildDomainAreasFromDB(areasDB, locale) { - return areasDB.map((areaDB) => - domainBuilder.buildArea({ - ...areaDB, - title: areaDB.title_i18n[locale], - }), - ); -} - -function _buildDomainCompetencesFromDB(competencesDB, locale) { - return competencesDB.map((competenceDB) => - domainBuilder.buildCompetence({ - ...competenceDB, - name: competenceDB.name_i18n[locale], - description: competenceDB.description_i18n[locale], - }), - ); -} - -function _buildDomainThematicsFromDB(thematicsDB, locale) { - return thematicsDB.map((thematicDB) => - domainBuilder.buildThematic({ - ...thematicDB, - name: thematicDB.name_i18n[locale], - }), - ); -} - -function _buildDomainTubesFromDB(tubesDB, locale) { - return tubesDB.map((tubeDB) => - domainBuilder.buildTube({ - ...tubeDB, - practicalTitle: tubeDB.practicalTitle_i18n[locale], - practicalDescription: tubeDB.practicalDescription_i18n[locale], - }), - ); -} - -function _buildDomainSkillsFromDB(skillsDB, locale) { - return skillsDB.map((skillDB) => - domainBuilder.buildSkill({ ...skillDB, difficulty: skillDB.level, hint: skillDB.hint_i18n[locale] }), - ); -} diff --git a/api/tests/learning-content/integration/infrastructure/repositories/learning-content-repository_test.js b/api/tests/learning-content/integration/infrastructure/repositories/learning-content-repository_test.js index ca2a45984fa..1ab2ddb02cc 100644 --- a/api/tests/learning-content/integration/infrastructure/repositories/learning-content-repository_test.js +++ b/api/tests/learning-content/integration/infrastructure/repositories/learning-content-repository_test.js @@ -1,20 +1,486 @@ -import { LearningContentRepository } from '../../../../../src/learning-content/infrastructure/repositories/learning-content-repository.js'; -import { expect } from '../../../../test-helper.js'; +import * as learningContentRepository from '../../../../../src/learning-content/infrastructure/repositories/learning-content-repository.js'; +import { NoSkillsInCampaignError, NotFoundError } from '../../../../../src/shared/domain/errors.js'; +import { catchErr, databaseBuilder, domainBuilder, expect } from '../../../../test-helper.js'; -describe('Learning Content | Integration | Repositories | Learning Content', function () { - describe('#saveMany', function () { - describe('when dtos are nullish', function () { - it('should do nothing', async function () { +describe('Integration | Repository | learning-content', function () { + let framework1Fr, framework1En, framework2Fr, framework2En; + let area1Fr, area1En, area2Fr, area2En; + let competence1Fr, competence1En, competence2Fr, competence2En, competence3Fr, competence3En; + let thematic1Fr, thematic1En, thematic2Fr, thematic2En, thematic3Fr, thematic3En; + let tube1Fr, tube1En, tube2Fr, tube2En, tube4Fr, tube4En; + let skill1Fr, skill2Fr, skill3Fr, skill8Fr; + + beforeEach(async function () { + const framework1DB = databaseBuilder.factory.learningContent.buildFramework({ + id: 'recFramework1', + name: 'Mon référentiel 1', + }); + const framework2DB = databaseBuilder.factory.learningContent.buildFramework({ + id: 'recFramework2', + name: 'Mon référentiel 2', + }); + const area1DB = databaseBuilder.factory.learningContent.buildArea({ + id: 'recArea1', + name: 'area1_name', + title_i18n: { fr: 'domaine1_TitreFr', en: 'area1_TitleEn' }, + color: 'area1_color', + code: 'area1_code', + frameworkId: 'recFramework1', + competenceIds: ['recCompetence1', 'recCompetence2'], + }); + const area2DB = databaseBuilder.factory.learningContent.buildArea({ + id: 'recArea2', + name: 'area2_name', + title_i18n: { fr: 'domaine2_TitreFr', en: 'area2_TitleEn' }, + color: 'area2_color', + code: 'area2_code', + frameworkId: 'recFramework2', + competenceIds: ['recCompetence3'], + }); + const competence1DB = databaseBuilder.factory.learningContent.buildCompetence({ + id: 'recCompetence1', + name_i18n: { fr: 'competence1_nomFr', en: 'competence1_nameEn' }, + index: '1', + description_i18n: { fr: 'competence1_descriptionFr', en: 'competence1_descriptionEn' }, + origin: 'Pix', + areaId: 'recArea1', + }); + const competence2DB = databaseBuilder.factory.learningContent.buildCompetence({ + id: 'recCompetence2', + name_i18n: { fr: 'competence2_nomFr', en: 'competence2_nameEn' }, + index: '2', + description_i18n: { fr: 'competence2_descriptionFr', en: 'competence2_descriptionEn' }, + origin: 'Pix', + areaId: 'recArea1', + }); + const competence3DB = databaseBuilder.factory.learningContent.buildCompetence({ + id: 'recCompetence3', + name_i18n: { fr: 'competence3_nomFr', en: 'competence3_nameEn' }, + index: '1', + description_i18n: { fr: 'competence3_descriptionFr', en: 'competence3_descriptionEn' }, + origin: 'Pix', + areaId: 'recArea2', + }); + const thematic1DB = databaseBuilder.factory.learningContent.buildThematic({ + id: 'recThematic1', + name_i18n: { + fr: 'thematique1_nomFr', + en: 'thematic1_nameEn', + }, + index: 10, + competenceId: 'recCompetence1', + tubeIds: ['recTube1'], + }); + const thematic2DB = databaseBuilder.factory.learningContent.buildThematic({ + id: 'recThematic2', + name_i18n: { + fr: 'thematique2_nomFr', + en: 'thematic2_nameEn', + }, + index: 20, + competenceId: 'recCompetence2', + tubeIds: ['recTube2', 'recTube3'], + }); + const thematic3DB = databaseBuilder.factory.learningContent.buildThematic({ + id: 'recThematic3', + name_i18n: { + fr: 'thematique3_nomFr', + en: 'thematic3_nameEn', + }, + index: 30, + competenceId: 'recCompetence3', + tubeIds: ['recTube4'], + }); + const tube1DB = databaseBuilder.factory.learningContent.buildTube({ + id: 'recTube1', + name: '@tube1_name', + title: 'tube1_title', + description: 'tube1_description', + practicalTitle_i18n: { fr: 'tube1_practicalTitleFr', en: 'tube1_practicalTitleEn' }, + practicalDescription_i18n: { + fr: 'tube1_practicalDescriptionFr', + en: 'tube1_practicalDescriptionEn', + }, + isMobileCompliant: true, + isTabletCompliant: false, + competenceId: 'recCompetence1', + thematicId: 'recThematic1', + }); + const tube2DB = databaseBuilder.factory.learningContent.buildTube({ + id: 'recTube2', + name: '@tube2_name', + title: '@tube2_title', + description: '@tube2_description', + practicalTitle_i18n: { fr: 'tube2_practicalTitleFr', en: 'tube2_practicalTitleEn' }, + practicalDescription_i18n: { + fr: 'tube2_practicalDescriptionFr', + en: 'tube2_practicalDescriptionEn', + }, + isMobileCompliant: false, + isTabletCompliant: true, + competenceId: 'recCompetence2', + thematicId: 'recThematic2', + }); + const tube3DB = databaseBuilder.factory.learningContent.buildTube({ + id: 'recTube3', + name: '@tube3_name', + title: '@tube3_title', + description: '@tube3_description', + practicalTitle_i18n: { fr: 'tube3_practicalTitleFr', en: 'tube3_practicalTitleEn' }, + practicalDescription_i18n: { + fr: 'tube3_practicalDescriptionFr', + en: 'tube3_practicalDescriptionEn', + }, + isMobileCompliant: true, + isTabletCompliant: true, + competenceId: 'recCompetence2', + thematicId: 'recThematic2', + }); + const tube4DB = databaseBuilder.factory.learningContent.buildTube({ + id: 'recTube4', + name: '@tube4_name', + title: 'tube4_title', + description: 'tube4_description', + practicalTitle_i18n: { fr: 'tube4_practicalTitleFr', en: 'tube4_practicalTitleEn' }, + practicalDescription_i18n: { + fr: 'tube4_practicalDescriptionFr', + en: 'tube4_practicalDescriptionEn', + }, + isMobileCompliant: false, + isTabletCompliant: false, + competenceId: 'recCompetence3', + thematicId: 'recThematic3', + }); + const skill1DB = databaseBuilder.factory.learningContent.buildSkill({ + id: 'recSkill1', + name: '@tube1_name4', + status: 'actif', + level: 4, + pixValue: 12, + version: 98, + tubeId: 'recTube1', + }); + const skill2DB = databaseBuilder.factory.learningContent.buildSkill({ + id: 'recSkill2', + name: '@tube2_name1', + status: 'actif', + level: 1, + pixValue: 34, + version: 76, + tubeId: 'recTube2', + }); + const skill3DB = databaseBuilder.factory.learningContent.buildSkill({ + id: 'recSkill3', + name: '@tube2_name2', + status: 'archivé', + level: 2, + pixValue: 56, + version: 54, + tubeId: 'recTube2', + }); + const skill4DB = databaseBuilder.factory.learningContent.buildSkill({ + id: 'recSkill4', + status: 'périmé', + tubeId: 'recTube2', + }); + const skill5DB = databaseBuilder.factory.learningContent.buildSkill({ + id: 'recSkill5', + name: '@tube3_name5', + status: 'archivé', + level: 5, + pixValue: 44, + version: 55, + tubeId: 'recTube3', + }); + const skill6DB = databaseBuilder.factory.learningContent.buildSkill({ + id: 'recSkill6', + status: 'périmé', + tubeId: 'recTube3', + }); + const skill7DB = databaseBuilder.factory.learningContent.buildSkill({ + id: 'recSkill7', + status: 'périmé', + tubeId: 'recTube3', + }); + const skill8DB = databaseBuilder.factory.learningContent.buildSkill({ + id: 'recSkill8', + name: '@tube4_name8', + status: 'actif', + level: 7, + pixValue: 78, + version: 32, + tubeId: 'recTube4', + }); + await databaseBuilder.commit(); + + [framework1Fr, framework2Fr] = _buildDomainFrameworksFromDB([framework1DB, framework2DB]); + [framework1En, framework2En] = _buildDomainFrameworksFromDB([framework1DB, framework2DB]); + [area1Fr, area2Fr] = _buildDomainAreasFromDB([area1DB, area2DB], 'fr'); + [area1En, area2En] = _buildDomainAreasFromDB([area1DB, area2DB], 'en'); + [competence1Fr, competence2Fr, competence3Fr] = _buildDomainCompetencesFromDB( + [competence1DB, competence2DB, competence3DB], + 'fr', + ); + [competence1En, competence2En, competence3En] = _buildDomainCompetencesFromDB( + [competence1DB, competence2DB, competence3DB], + 'en', + ); + [thematic1Fr, thematic2Fr, thematic3Fr] = _buildDomainThematicsFromDB( + [thematic1DB, thematic2DB, thematic3DB], + 'fr', + ); + [thematic1En, thematic2En, thematic3En] = _buildDomainThematicsFromDB( + [thematic1DB, thematic2DB, thematic3DB], + 'en', + ); + [tube1Fr, tube2Fr, , tube4Fr] = _buildDomainTubesFromDB([tube1DB, tube2DB, tube3DB, tube4DB], 'fr'); + [tube1En, tube2En, , tube4En] = _buildDomainTubesFromDB([tube1DB, tube2DB, tube3DB, tube4DB], 'en'); + [skill1Fr, skill2Fr, skill3Fr, , , , , skill8Fr] = _buildDomainSkillsFromDB( + [skill1DB, skill2DB, skill3DB, skill4DB, skill5DB, skill6DB, skill7DB, skill8DB], + 'fr', + ); + }); + + describe('#findByCampaignId', function () { + let campaignId; + + it('should return frameworks, areas, competences, thematics and tubes of the skills hierarchy', async function () { + // given + campaignId = databaseBuilder.factory.buildCampaign().id; + ['recSkill2', 'recSkill3'].forEach((skillId) => + databaseBuilder.factory.buildCampaignSkill({ campaignId, skillId }), + ); + await databaseBuilder.commit(); + + framework1Fr.areas = [area1Fr]; + area1Fr.competences = [competence2Fr]; + competence2Fr.thematics = [thematic2Fr]; + competence2Fr.tubes = [tube2Fr]; + thematic2Fr.tubes = [tube2Fr]; + tube2Fr.skills = [skill2Fr, skill3Fr]; + + // when + const learningContentFromCampaign = await learningContentRepository.findByCampaignId(campaignId); + + // then + expect(learningContentFromCampaign.areas).to.deep.equal([area1Fr]); + expect(learningContentFromCampaign.frameworks).to.deep.equal([framework1Fr]); + }); + + it('should translate names and descriptions when specifying a locale', async function () { + // given + campaignId = databaseBuilder.factory.buildCampaign().id; + ['recSkill2', 'recSkill3'].forEach((skillId) => + databaseBuilder.factory.buildCampaignSkill({ campaignId, skillId }), + ); + await databaseBuilder.commit(); + + framework1En.areas = [area1En]; + area1En.competences = [competence2En]; + competence2En.thematics = [thematic2En]; + competence2En.tubes = [tube2En]; + thematic2En.tubes = [tube2En]; + tube2En.skills = [skill2Fr, skill3Fr]; + + // when + const learningContentFromCampaign = await learningContentRepository.findByCampaignId(campaignId, 'en'); + + // then + expect(learningContentFromCampaign.frameworks).to.deep.equal([framework1En]); + }); + + it('should throw a NoSkillsInCampaignError when there are no more operative skills', async function () { + // given + campaignId = databaseBuilder.factory.buildCampaign().id; + databaseBuilder.factory.buildCampaignSkill({ campaignId, skillId: 'recSkill4' }); + await databaseBuilder.commit(); + + // when + const err = await catchErr(learningContentRepository.findByCampaignId)(campaignId); + + // then + expect(err).to.be.instanceOf(NoSkillsInCampaignError); + }); + }); + + describe('#findByTargetProfileId', function () { + context('when target profile does not have capped tubes', function () { + it('should throw a NotFound error', async function () { + // given + const targetProfileId = databaseBuilder.factory.buildTargetProfile().id; + const anotherTargetProfileId = databaseBuilder.factory.buildTargetProfile().id; + databaseBuilder.factory.buildTargetProfileTube({ targetProfileId: anotherTargetProfileId }); + await databaseBuilder.commit(); + + // when + const error = await catchErr(learningContentRepository.findByTargetProfileId)(targetProfileId); + + // then + expect(error).to.be.instanceOf(NotFoundError); + expect(error.message).to.equal("Le profil cible n'existe pas"); + }); + }); + + context('when target profile has capped tubes', function () { + it('should return frameworks, areas, competences, thematics and tubes of the active skills hierarchy with default FR language', async function () { // given - const repository = new LearningContentRepository({ tableName: 'TEST' }); - const dtos = undefined; + const targetProfileId = databaseBuilder.factory.buildTargetProfile().id; + databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'recTube2', level: 2 }); + await databaseBuilder.commit(); + + framework1Fr.areas = [area1Fr]; + area1Fr.competences = [competence2Fr]; + competence2Fr.thematics = [thematic2Fr]; + competence2Fr.tubes = [tube2Fr]; + thematic2Fr.tubes = [tube2Fr]; + tube2Fr.skills = [skill2Fr]; // when - const result = await repository.saveMany(dtos); + const targetProfileLearningContent = await learningContentRepository.findByTargetProfileId(targetProfileId); // then - expect(result).to.be.undefined; + expect(targetProfileLearningContent.frameworks).to.deep.equal([framework1Fr]); + }); + + context('when using a specific locale', function () { + it('should translate names and descriptions', async function () { + // given + const targetProfileId = databaseBuilder.factory.buildTargetProfile().id; + databaseBuilder.factory.buildTargetProfileTube({ targetProfileId, tubeId: 'recTube2', level: 2 }); + await databaseBuilder.commit(); + + framework1En.areas = [area1En]; + area1En.competences = [competence2En]; + competence2En.thematics = [thematic2En]; + competence2En.tubes = [tube2En]; + thematic2En.tubes = [tube2En]; + tube2En.skills = [skill2Fr]; + + // when + const targetProfileLearningContent = await learningContentRepository.findByTargetProfileId( + targetProfileId, + 'en', + ); + + // then + expect(targetProfileLearningContent.frameworks).to.deep.equal([framework1En]); + }); + }); + }); + }); + + describe('#findByFrameworkNames', function () { + it('should return an active LearningContent with the frameworks designated by name', async function () { + // given + framework1Fr.areas = [area1Fr]; + framework2Fr.areas = [area2Fr]; + area1Fr.competences = [competence1Fr, competence2Fr]; + area2Fr.competences = [competence3Fr]; + competence1Fr.thematics = [thematic1Fr]; + competence1Fr.tubes = [tube1Fr]; + competence2Fr.thematics = [thematic2Fr]; + competence2Fr.tubes = [tube2Fr]; + competence3Fr.thematics = [thematic3Fr]; + competence3Fr.tubes = [tube4Fr]; + thematic1Fr.tubes = [tube1Fr]; + thematic2Fr.tubes = [tube2Fr]; + thematic3Fr.tubes = [tube4Fr]; + tube1Fr.skills = [skill1Fr]; + tube2Fr.skills = [skill2Fr]; + tube4Fr.skills = [skill8Fr]; + + // when + const learningContent = await learningContentRepository.findByFrameworkNames({ + frameworkNames: ['Mon référentiel 1', 'Mon référentiel 2'], + }); + + // then + const expectedLearningContent = domainBuilder.buildLearningContent([framework1Fr, framework2Fr]); + expect(learningContent).to.deepEqualInstance(expectedLearningContent); + }); + + it('should return an active LearningContent in the given language', async function () { + // given + framework1En.areas = [area1En]; + framework2En.areas = [area2En]; + area1En.competences = [competence1En, competence2En]; + area2En.competences = [competence3En]; + competence1En.thematics = [thematic1En]; + competence1En.tubes = [tube1En]; + competence2En.thematics = [thematic2En]; + competence2En.tubes = [tube2En]; + competence3En.thematics = [thematic3En]; + competence3En.tubes = [tube4En]; + thematic1En.tubes = [tube1En]; + thematic2En.tubes = [tube2En]; + thematic3En.tubes = [tube4En]; + tube1En.skills = [skill1Fr]; + tube2En.skills = [skill2Fr]; + tube4En.skills = [skill8Fr]; + + // when + const learningContent = await learningContentRepository.findByFrameworkNames({ + frameworkNames: ['Mon référentiel 1', 'Mon référentiel 2'], + locale: 'en', }); + + // then + const expectedLearningContent = domainBuilder.buildLearningContent([framework1En, framework2En]); + expect(learningContent).to.deepEqualInstance(expectedLearningContent); }); }); }); + +function _buildDomainFrameworksFromDB(frameworksDB) { + return frameworksDB.map((frameworkDB) => + domainBuilder.buildFramework({ + id: frameworkDB.id, + name: frameworkDB.name, + areas: [], + }), + ); +} + +function _buildDomainAreasFromDB(areasDB, locale) { + return areasDB.map((areaDB) => + domainBuilder.buildArea({ + ...areaDB, + title: areaDB.title_i18n[locale], + }), + ); +} + +function _buildDomainCompetencesFromDB(competencesDB, locale) { + return competencesDB.map((competenceDB) => + domainBuilder.buildCompetence({ + ...competenceDB, + name: competenceDB.name_i18n[locale], + description: competenceDB.description_i18n[locale], + }), + ); +} + +function _buildDomainThematicsFromDB(thematicsDB, locale) { + return thematicsDB.map((thematicDB) => + domainBuilder.buildThematic({ + ...thematicDB, + name: thematicDB.name_i18n[locale], + }), + ); +} + +function _buildDomainTubesFromDB(tubesDB, locale) { + return tubesDB.map((tubeDB) => + domainBuilder.buildTube({ + ...tubeDB, + practicalTitle: tubeDB.practicalTitle_i18n[locale], + practicalDescription: tubeDB.practicalDescription_i18n[locale], + }), + ); +} + +function _buildDomainSkillsFromDB(skillsDB, locale) { + return skillsDB.map((skillDB) => + domainBuilder.buildSkill({ ...skillDB, difficulty: skillDB.level, hint: skillDB.hint_i18n[locale] }), + ); +} diff --git a/api/tests/unit/domain/services/learning-content/learning-content-conversion-service_test.js b/api/tests/learning-content/unit/domain/services/learning-content-conversion-service_test.js similarity index 93% rename from api/tests/unit/domain/services/learning-content/learning-content-conversion-service_test.js rename to api/tests/learning-content/unit/domain/services/learning-content-conversion-service_test.js index 742cd9da9bc..919a9e0157f 100644 --- a/api/tests/unit/domain/services/learning-content/learning-content-conversion-service_test.js +++ b/api/tests/learning-content/unit/domain/services/learning-content-conversion-service_test.js @@ -1,4 +1,4 @@ -import { findActiveSkillsForCappedTubes } from '../../../../../lib/domain/services/learning-content/learning-content-conversion-service.js'; +import { findActiveSkillsForCappedTubes } from '../../../../../src/learning-content/domain/services/learning-content-conversion-service.js'; import { domainBuilder, expect, sinon } from '../../../../test-helper.js'; describe('Unit | Service | learning-content-conversion-service', function () {