From efe3d22b06dbe290a007782e3dd8eeff4fd1a16f Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Thu, 21 Aug 2025 19:57:45 +0200 Subject: [PATCH] fix: make sure ext curriculum tests fail if missing meta (#61904) --- .../build-external-curricula-data-v1.test.ts | 12 +- .../scripts/build/external-data-schema-v1.js | 119 +++++++++--------- .../scripts/build/external-data-schema-v2.js | 119 +++++++++--------- 3 files changed, 134 insertions(+), 116 deletions(-) diff --git a/tools/scripts/build/build-external-curricula-data-v1.test.ts b/tools/scripts/build/build-external-curricula-data-v1.test.ts index 4216302701d..9988bdbc5ca 100644 --- a/tools/scripts/build/build-external-curricula-data-v1.test.ts +++ b/tools/scripts/build/build-external-curricula-data-v1.test.ts @@ -72,14 +72,18 @@ ${result.error.message}` await readdirp.promise(`${clientStaticPath}/curriculum-data/${VERSION}`, { directoryFilter: ['!challenges'], fileFilter: entry => { + // path without extension: + const filePath = entry.path.replace(/\.json$/, ''); // The directory contains super block files and other curriculum-related files. // We're only interested in super block ones. const superBlocks = Object.values(SuperBlocks); - return superBlocks.includes(entry.basename); + return superBlocks.includes(filePath); } }) ).map(file => file.path); + expect(fileArray.length).toBeGreaterThan(0); + fileArray.forEach(fileInArray => { const fileContent = fs.readFileSync( `${clientStaticPath}/curriculum-data/${VERSION}/${fileInArray}`, @@ -100,14 +104,18 @@ ${result.error.message}`); await readdirp.promise(`${clientStaticPath}/curriculum-data/${VERSION}`, { directoryFilter: ['!challenges'], fileFilter: entry => { + // path without extension: + const filePath = entry.path.replace(/\.json$/, ''); // The directory contains super block files and other curriculum-related files. // We're only interested in super block ones. const superBlocks = Object.values(SuperBlocks); - return superBlocks.includes(entry.basename); + return superBlocks.includes(filePath); } }) ).map(file => file.path); + expect(superBlockFiles.length).toBeGreaterThan(0); + superBlockFiles.forEach(file => { const fileContentJson = fs.readFileSync( `${clientStaticPath}/curriculum-data/${VERSION}/${file}`, diff --git a/tools/scripts/build/external-data-schema-v1.js b/tools/scripts/build/external-data-schema-v1.js index deb0417d9c5..e09c7727ff7 100644 --- a/tools/scripts/build/external-data-schema-v1.js +++ b/tools/scripts/build/external-data-schema-v1.js @@ -2,63 +2,68 @@ const Joi = require('joi'); const blockSchema = Joi.object({}).keys({ desc: Joi.array().min(1), - challenges: Joi.object({}).keys({ - name: Joi.string().required(), - isUpcomingChange: Joi.bool().required(), - usesMultifileEditor: Joi.bool().optional(), - hasEditableBoundaries: Joi.bool().optional(), - dashedName: Joi.string().required(), - helpCategory: Joi.valid( - 'JavaScript', - 'HTML-CSS', - 'Python', - 'Backend Development', - 'C-Sharp', - 'English', - 'Odin', - 'Euler', - 'Rosetta' - ).required(), - order: Joi.number().when('superBlock', { - is: 'full-stack-developer', - then: Joi.forbidden(), - otherwise: Joi.required() - }), - template: Joi.string().allow(''), - required: Joi.array(), - superBlock: Joi.string().required(), - blockLayout: Joi.valid( - 'challenge-list', - 'challenge-grid', - 'dialogue-grid', - 'link', - 'project-list', - 'legacy-challenge-list', - 'legacy-link', - 'legacy-challenge-grid' - ).required(), - blockType: Joi.valid( - 'lecture', - 'workshop', - 'lab', - 'review', - 'quiz', - 'exam' - ).when('superBlock', { - is: 'full-stack-developer', - then: Joi.required(), - otherwise: Joi.optional() - }), - challengeOrder: Joi.array().items( - Joi.object({}).keys({ - id: Joi.string(), - title: Joi.string() - }) - ), - disableLoopProtectTests: Joi.boolean(), - disableLoopProtectPreview: Joi.boolean(), - superOrder: Joi.number() - }) + challenges: Joi.object({}) + .keys({ + name: Joi.string().required(), + isUpcomingChange: Joi.bool().required(), + usesMultifileEditor: Joi.bool().optional(), + hasEditableBoundaries: Joi.bool().optional(), + dashedName: Joi.string().required(), + helpCategory: Joi.valid( + 'JavaScript', + 'HTML-CSS', + 'Python', + 'Backend Development', + 'C-Sharp', + 'English', + 'Odin', + 'Euler', + 'Rosetta' + ).required(), + order: Joi.number().when('superBlock', { + is: 'full-stack-developer', + then: Joi.forbidden(), + otherwise: Joi.required() + }), + template: Joi.string().allow(''), + required: Joi.array(), + superBlock: Joi.string().required(), + blockLayout: Joi.valid( + 'challenge-list', + 'challenge-grid', + 'dialogue-grid', + 'link', + 'project-list', + 'legacy-challenge-list', + 'legacy-link', + 'legacy-challenge-grid' + ).required(), + blockType: Joi.valid( + 'lecture', + 'workshop', + 'lab', + 'review', + 'quiz', + 'exam' + ).when('superBlock', { + is: 'full-stack-developer', + then: Joi.required(), + otherwise: Joi.optional() + }), + challengeOrder: Joi.array() + .items( + Joi.object({}).keys({ + id: Joi.string(), + title: Joi.string() + }) + ) + .min(1) + .required(), + disableLoopProtectTests: Joi.boolean(), + disableLoopProtectPreview: Joi.boolean(), + superOrder: Joi.number() + }) + .required() }); const subSchema = Joi.object({}).keys({ diff --git a/tools/scripts/build/external-data-schema-v2.js b/tools/scripts/build/external-data-schema-v2.js index 0a024b08018..68e5447999b 100644 --- a/tools/scripts/build/external-data-schema-v2.js +++ b/tools/scripts/build/external-data-schema-v2.js @@ -7,63 +7,68 @@ const slugRE = new RegExp('^[a-z0-9-]+$'); const blockSchema = Joi.object().keys({ intro: Joi.array().min(1), - meta: Joi.object({}).keys({ - name: Joi.string().required(), - isUpcomingChange: Joi.bool().required(), - usesMultifileEditor: Joi.bool().optional(), - hasEditableBoundaries: Joi.bool().optional(), - dashedName: Joi.string().required(), - helpCategory: Joi.valid( - 'JavaScript', - 'HTML-CSS', - 'Python', - 'Backend Development', - 'C-Sharp', - 'English', - 'Odin', - 'Euler', - 'Rosetta' - ).required(), - order: Joi.number().when('superBlock', { - is: chapterBasedSuperBlocks, - then: Joi.forbidden(), - otherwise: Joi.required() - }), - template: Joi.string().allow(''), - required: Joi.array(), - superBlock: Joi.string().required(), - blockLayout: Joi.valid( - 'challenge-list', - 'challenge-grid', - 'dialogue-grid', - 'link', - 'project-list', - 'legacy-challenge-list', - 'legacy-link', - 'legacy-challenge-grid' - ).required(), - blockType: Joi.valid( - 'lecture', - 'workshop', - 'lab', - 'review', - 'quiz', - 'exam' - ).when('superBlock', { - is: chapterBasedSuperBlocks, - then: Joi.required(), - otherwise: Joi.optional() - }), - challengeOrder: Joi.array().items( - Joi.object({}).keys({ - id: Joi.string(), - title: Joi.string() - }) - ), - disableLoopProtectTests: Joi.boolean(), - disableLoopProtectPreview: Joi.boolean(), - superOrder: Joi.number() - }) + meta: Joi.object({}) + .keys({ + name: Joi.string().required(), + isUpcomingChange: Joi.bool().required(), + usesMultifileEditor: Joi.bool().optional(), + hasEditableBoundaries: Joi.bool().optional(), + dashedName: Joi.string().required(), + helpCategory: Joi.valid( + 'JavaScript', + 'HTML-CSS', + 'Python', + 'Backend Development', + 'C-Sharp', + 'English', + 'Odin', + 'Euler', + 'Rosetta' + ).required(), + order: Joi.number().when('superBlock', { + is: chapterBasedSuperBlocks, + then: Joi.forbidden(), + otherwise: Joi.required() + }), + template: Joi.string().allow(''), + required: Joi.array(), + superBlock: Joi.string().required(), + blockLayout: Joi.valid( + 'challenge-list', + 'challenge-grid', + 'dialogue-grid', + 'link', + 'project-list', + 'legacy-challenge-list', + 'legacy-link', + 'legacy-challenge-grid' + ).required(), + blockType: Joi.valid( + 'lecture', + 'workshop', + 'lab', + 'review', + 'quiz', + 'exam' + ).when('superBlock', { + is: chapterBasedSuperBlocks, + then: Joi.required(), + otherwise: Joi.optional() + }), + challengeOrder: Joi.array().items( + Joi.object({}) + .keys({ + id: Joi.string(), + title: Joi.string() + }) + .min(1) + .required() + ), + disableLoopProtectTests: Joi.boolean(), + disableLoopProtectPreview: Joi.boolean(), + superOrder: Joi.number() + }) + .required() }); const blockBasedCurriculumSchema = Joi.object().pattern(