diff --git a/api/db/database-builder/factory/learning-content/build-challenge.js b/api/db/database-builder/factory/learning-content/build-challenge.js index 9a2dbb50d41..1c9b239b3b9 100644 --- a/api/db/database-builder/factory/learning-content/build-challenge.js +++ b/api/db/database-builder/factory/learning-content/build-challenge.js @@ -38,6 +38,8 @@ export function buildChallenge({ locales = ['fr'], competenceId = null, skillId = null, + hasEmbedInternalValidation = false, + noValidationNeeded = false, } = {}) { return buildChallengeInDB({ id, @@ -77,6 +79,8 @@ export function buildChallenge({ locales, competenceId, skillId, + hasEmbedInternalValidation, + noValidationNeeded, }); } @@ -118,6 +122,8 @@ export function buildChallengeWithNoDefaultValues({ locales, competenceId, skillId, + hasEmbedInternalValidation, + noValidationNeeded, }) { return buildChallengeInDB({ id, @@ -157,6 +163,8 @@ export function buildChallengeWithNoDefaultValues({ locales, competenceId, skillId, + hasEmbedInternalValidation, + noValidationNeeded, }); } @@ -198,6 +206,8 @@ function buildChallengeInDB({ locales, competenceId, skillId, + hasEmbedInternalValidation, + noValidationNeeded, }) { const values = { id, @@ -237,6 +247,8 @@ function buildChallengeInDB({ locales, competenceId, skillId, + hasEmbedInternalValidation, + noValidationNeeded, }; return databaseBuffer.pushInsertable({ tableName: 'learningcontent.challenges', diff --git a/api/db/migrations/20250129071223_add-has-embed-internal-validation-to-challenges.js b/api/db/migrations/20250129071223_add-has-embed-internal-validation-to-challenges.js new file mode 100644 index 00000000000..d1e9bc6e4a3 --- /dev/null +++ b/api/db/migrations/20250129071223_add-has-embed-internal-validation-to-challenges.js @@ -0,0 +1,20 @@ +const SCHEMA_NAME = 'learningcontent'; +const TABLE_NAME = 'challenges'; +const COLUMN_NAME = 'hasEmbedInternalValidation'; + +const up = async function (knex) { + await knex.schema.withSchema(SCHEMA_NAME).table(TABLE_NAME, function (table) { + table + .boolean(COLUMN_NAME) + .defaultTo(false) + .comment('Indicates that the embed has internal rules to handle the challenge validation'); + }); +}; + +const down = async function (knex) { + await knex.schema.withSchema(SCHEMA_NAME).table(TABLE_NAME, function (table) { + table.dropColumn(COLUMN_NAME); + }); +}; + +export { down, up }; diff --git a/api/db/migrations/20250129071224_add-no-validation-needed-to-challenges.js b/api/db/migrations/20250129071224_add-no-validation-needed-to-challenges.js new file mode 100644 index 00000000000..a9864a0d8ae --- /dev/null +++ b/api/db/migrations/20250129071224_add-no-validation-needed-to-challenges.js @@ -0,0 +1,22 @@ +const SCHEMA_NAME = 'learningcontent'; +const TABLE_NAME = 'challenges'; +const COLUMN_NAME = 'noValidationNeeded'; + +const up = async function (knex) { + await knex.schema.withSchema(SCHEMA_NAME).table(TABLE_NAME, function (table) { + table + .boolean(COLUMN_NAME) + .defaultTo(false) + .comment( + 'Indicates that the challenge does not need any validation, i.e. contains only a video to watch or a text to read', + ); + }); +}; + +const down = async function (knex) { + await knex.schema.withSchema(SCHEMA_NAME).table(TABLE_NAME, function (table) { + table.dropColumn(COLUMN_NAME); + }); +}; + +export { down, up }; diff --git a/api/src/learning-content/infrastructure/repositories/challenge-repository.js b/api/src/learning-content/infrastructure/repositories/challenge-repository.js index 62d40d68507..fb7b03e5356 100644 --- a/api/src/learning-content/infrastructure/repositories/challenge-repository.js +++ b/api/src/learning-content/infrastructure/repositories/challenge-repository.js @@ -47,6 +47,8 @@ class ChallengeRepository extends LearningContentRepository { locales, competenceId, skillId, + hasEmbedInternalValidation, + noValidationNeeded, }) { return { id, @@ -86,6 +88,8 @@ class ChallengeRepository extends LearningContentRepository { locales, competenceId, skillId, + hasEmbedInternalValidation, + noValidationNeeded, }; } diff --git a/api/tests/learning-content/integration/application/jobs/lcms-create-release-job-controller_test.js b/api/tests/learning-content/integration/application/jobs/lcms-create-release-job-controller_test.js index d5ac7c4ecca..30c59ec0af3 100644 --- a/api/tests/learning-content/integration/application/jobs/lcms-create-release-job-controller_test.js +++ b/api/tests/learning-content/integration/application/jobs/lcms-create-release-job-controller_test.js @@ -294,6 +294,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr'], competenceId: 'competenceId About to be refreshed Epreuve - new', skillId: 'skillId About to be refreshed Epreuve - new', + hasEmbedInternalValidation: false, + noValidationNeeded: false, }, { id: 'untouchedChallengeId', @@ -333,6 +335,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr', 'en'], competenceId: 'competenceId Untouched Epreuve', skillId: 'skillId Untouched Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, { id: 'newChallengeId', @@ -372,6 +376,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr', 'nl'], competenceId: 'competenceId New Epreuve', skillId: 'skillId New Epreuve', + hasEmbedInternalValidation: true, + noValidationNeeded: false, }, ], courses: [ @@ -786,6 +792,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr', 'nl'], competenceId: 'competenceId About to be refreshed Epreuve - old', skillId: 'skillId About to be refreshed Epreuve - old', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }); databaseBuilder.factory.learningContent.buildChallenge({ id: 'untouchedChallengeId', @@ -825,6 +833,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr', 'en'], competenceId: 'competenceId Untouched Epreuve', skillId: 'skillId Untouched Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }); databaseBuilder.factory.learningContent.buildChallenge({ id: 'missingChallengeId', @@ -864,6 +874,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['en', 'nl'], competenceId: 'competenceId Missing Epreuve', skillId: 'skillId Missing Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: false, }); databaseBuilder.factory.learningContent.buildCourse({ id: 'aboutToBeRefreshedCourseId', @@ -1394,6 +1406,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr'], competenceId: 'competenceId About to be refreshed Epreuve - new', skillId: 'skillId About to be refreshed Epreuve - new', + hasEmbedInternalValidation: false, + noValidationNeeded: false, }, { id: 'missingChallengeId', @@ -1433,6 +1447,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['en', 'nl'], competenceId: 'competenceId Missing Epreuve', skillId: 'skillId Missing Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: false, }, { id: 'newChallengeId', @@ -1472,6 +1488,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr', 'nl'], competenceId: 'competenceId New Epreuve', skillId: 'skillId New Epreuve', + hasEmbedInternalValidation: true, + noValidationNeeded: false, }, { id: 'untouchedChallengeId', @@ -1511,6 +1529,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr', 'en'], competenceId: 'competenceId Untouched Epreuve', skillId: 'skillId Untouched Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, ]); const updatedCourses = await knex.select('*').from('learningcontent.courses').orderBy('id'); @@ -2004,6 +2024,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr', 'nl'], competenceId: 'competenceId About to be refreshed Epreuve - old', skillId: 'skillId About to be refreshed Epreuve - old', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }, { id: 'missingChallengeId', @@ -2043,6 +2065,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['en', 'nl'], competenceId: 'competenceId Missing Epreuve', skillId: 'skillId Missing Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: false, }, { id: 'untouchedChallengeId', @@ -2082,6 +2106,8 @@ describe('Learning Content | Integration | Application | Jobs | Create release', locales: ['fr', 'en'], competenceId: 'competenceId Untouched Epreuve', skillId: 'skillId Untouched Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, ]); const updatedCourses = await knex.select('*').from('learningcontent.courses').orderBy('id'); diff --git a/api/tests/learning-content/integration/application/jobs/lcms-refresh-cache-job-controller_test.js b/api/tests/learning-content/integration/application/jobs/lcms-refresh-cache-job-controller_test.js index d17be52ed25..66266a511b1 100644 --- a/api/tests/learning-content/integration/application/jobs/lcms-refresh-cache-job-controller_test.js +++ b/api/tests/learning-content/integration/application/jobs/lcms-refresh-cache-job-controller_test.js @@ -294,6 +294,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr'], competenceId: 'competenceId About to be refreshed Epreuve - new', skillId: 'skillId About to be refreshed Epreuve - new', + hasEmbedInternalValidation: false, + noValidationNeeded: false, }, { id: 'untouchedChallengeId', @@ -333,6 +335,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr', 'en'], competenceId: 'competenceId Untouched Epreuve', skillId: 'skillId Untouched Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, { id: 'newChallengeId', @@ -372,6 +376,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr', 'nl'], competenceId: 'competenceId New Epreuve', skillId: 'skillId New Epreuve', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }, ], courses: [ @@ -786,6 +792,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr', 'nl'], competenceId: 'competenceId About to be refreshed Epreuve - old', skillId: 'skillId About to be refreshed Epreuve - old', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }); databaseBuilder.factory.learningContent.buildChallenge({ id: 'untouchedChallengeId', @@ -825,6 +833,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr', 'en'], competenceId: 'competenceId Untouched Epreuve', skillId: 'skillId Untouched Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }); databaseBuilder.factory.learningContent.buildChallenge({ id: 'missingChallengeId', @@ -864,6 +874,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['en', 'nl'], competenceId: 'competenceId Missing Epreuve', skillId: 'skillId Missing Epreuve', + hasEmbedInternalValidation: true, + noValidationNeeded: false, }); databaseBuilder.factory.learningContent.buildCourse({ id: 'aboutToBeRefreshedCourseId', @@ -1394,6 +1406,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr'], competenceId: 'competenceId About to be refreshed Epreuve - new', skillId: 'skillId About to be refreshed Epreuve - new', + hasEmbedInternalValidation: false, + noValidationNeeded: false, }, { id: 'missingChallengeId', @@ -1433,6 +1447,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['en', 'nl'], competenceId: 'competenceId Missing Epreuve', skillId: 'skillId Missing Epreuve', + hasEmbedInternalValidation: true, + noValidationNeeded: false, }, { id: 'newChallengeId', @@ -1472,6 +1488,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr', 'nl'], competenceId: 'competenceId New Epreuve', skillId: 'skillId New Epreuve', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }, { id: 'untouchedChallengeId', @@ -1511,6 +1529,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr', 'en'], competenceId: 'competenceId Untouched Epreuve', skillId: 'skillId Untouched Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, ]); const refreshedCourses = await knex.select('*').from('learningcontent.courses').orderBy('id'); @@ -2004,6 +2024,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr', 'nl'], competenceId: 'competenceId About to be refreshed Epreuve - old', skillId: 'skillId About to be refreshed Epreuve - old', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }, { id: 'missingChallengeId', @@ -2043,6 +2065,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['en', 'nl'], competenceId: 'competenceId Missing Epreuve', skillId: 'skillId Missing Epreuve', + hasEmbedInternalValidation: true, + noValidationNeeded: false, }, { id: 'untouchedChallengeId', @@ -2082,6 +2106,8 @@ describe('Learning Content | Integration | Application | Jobs | Refresh cache', locales: ['fr', 'en'], competenceId: 'competenceId Untouched Epreuve', skillId: 'skillId Untouched Epreuve', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, ]); const refreshedCourses = await knex.select('*').from('learningcontent.courses').orderBy('id'); diff --git a/api/tests/learning-content/integration/infrastructure/repositories/challenge-repository_test.js b/api/tests/learning-content/integration/infrastructure/repositories/challenge-repository_test.js index 90f095f20a0..139bc70a941 100644 --- a/api/tests/learning-content/integration/infrastructure/repositories/challenge-repository_test.js +++ b/api/tests/learning-content/integration/infrastructure/repositories/challenge-repository_test.js @@ -48,6 +48,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr'], competenceId: 'competenceIdA', skillId: 'skillIdA', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }, { id: 'challengeIdB', @@ -87,6 +89,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['en', 'nl'], competenceId: 'competenceIdB', skillId: 'skillIdB', + hasEmbedInternalValidation: true, + noValidationNeeded: false, }, { id: 'challengeIdC', @@ -126,6 +130,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['en'], competenceId: 'competenceIdC', skillId: 'skillIdC', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, ]; @@ -174,6 +180,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr'], competenceId: 'competenceIdA', skillId: 'skillIdA', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }, { id: 'challengeIdB', @@ -213,6 +221,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['en', 'nl'], competenceId: 'competenceIdB', skillId: 'skillIdB', + hasEmbedInternalValidation: true, + noValidationNeeded: false, }, { id: 'challengeIdC', @@ -252,6 +262,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['en'], competenceId: 'competenceIdC', skillId: 'skillIdC', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, ]); }); @@ -297,6 +309,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr'], competenceId: 'competenceIdA', skillId: 'skillIdA', + hasEmbedInternalValidation: true, + noValidationNeeded: false, }); databaseBuilder.factory.learningContent.buildChallenge({ id: 'challengeIdB', @@ -336,6 +350,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['en', 'nl'], competenceId: 'competenceIdB', skillId: 'skillIdB', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }); await databaseBuilder.commit(); @@ -378,6 +394,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr', 'fr-fr'], competenceId: 'competenceIdA modified', skillId: 'skillIdA modified', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, { id: 'challengeIdC', @@ -417,6 +435,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['en'], competenceId: 'competenceIdC', skillId: 'skillIdC', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }, ]; @@ -465,6 +485,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr', 'fr-fr'], competenceId: 'competenceIdA modified', skillId: 'skillIdA modified', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, { id: 'challengeIdB', @@ -504,6 +526,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['en', 'nl'], competenceId: 'competenceIdB', skillId: 'skillIdB', + hasEmbedInternalValidation: false, + noValidationNeeded: true, }, { id: 'challengeIdC', @@ -543,6 +567,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['en'], competenceId: 'competenceIdC', skillId: 'skillIdC', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }, ]); }); @@ -595,6 +621,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr'], competenceId: 'competenceIdA', skillId: 'skillIdA', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }; // when @@ -646,6 +674,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr'], competenceId: 'competenceIdA', skillId: 'skillIdA', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }); }); @@ -689,6 +719,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr'], competenceId: 'competenceIdA', skillId: 'skillIdA', + hasEmbedInternalValidation: true, + noValidationNeeded: true, }); await databaseBuilder.commit(); const challengeDto = { @@ -729,6 +761,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr', 'fr-fr'], competenceId: 'competenceIdA modified', skillId: 'skillIdA modified', + hasEmbedInternalValidation: false, + noValidationNeeded: false, }; // when @@ -780,6 +814,8 @@ describe('Learning Content | Integration | Repositories | Challenge', function ( locales: ['fr', 'fr-fr'], competenceId: 'competenceIdA modified', skillId: 'skillIdA modified', + hasEmbedInternalValidation: false, + noValidationNeeded: false, }); }); });