fix: make sure ext curriculum tests fail if missing meta (#61904)

This commit is contained in:
Oliver Eyton-Williams
2025-08-21 19:57:45 +02:00
committed by GitHub
parent b246d99b30
commit efe3d22b06
3 changed files with 134 additions and 116 deletions
@@ -72,14 +72,18 @@ ${result.error.message}`
await readdirp.promise(`${clientStaticPath}/curriculum-data/${VERSION}`, { await readdirp.promise(`${clientStaticPath}/curriculum-data/${VERSION}`, {
directoryFilter: ['!challenges'], directoryFilter: ['!challenges'],
fileFilter: entry => { fileFilter: entry => {
// path without extension:
const filePath = entry.path.replace(/\.json$/, '');
// The directory contains super block files and other curriculum-related files. // The directory contains super block files and other curriculum-related files.
// We're only interested in super block ones. // We're only interested in super block ones.
const superBlocks = Object.values(SuperBlocks); const superBlocks = Object.values(SuperBlocks);
return superBlocks.includes(entry.basename); return superBlocks.includes(filePath);
} }
}) })
).map(file => file.path); ).map(file => file.path);
expect(fileArray.length).toBeGreaterThan(0);
fileArray.forEach(fileInArray => { fileArray.forEach(fileInArray => {
const fileContent = fs.readFileSync( const fileContent = fs.readFileSync(
`${clientStaticPath}/curriculum-data/${VERSION}/${fileInArray}`, `${clientStaticPath}/curriculum-data/${VERSION}/${fileInArray}`,
@@ -100,14 +104,18 @@ ${result.error.message}`);
await readdirp.promise(`${clientStaticPath}/curriculum-data/${VERSION}`, { await readdirp.promise(`${clientStaticPath}/curriculum-data/${VERSION}`, {
directoryFilter: ['!challenges'], directoryFilter: ['!challenges'],
fileFilter: entry => { fileFilter: entry => {
// path without extension:
const filePath = entry.path.replace(/\.json$/, '');
// The directory contains super block files and other curriculum-related files. // The directory contains super block files and other curriculum-related files.
// We're only interested in super block ones. // We're only interested in super block ones.
const superBlocks = Object.values(SuperBlocks); const superBlocks = Object.values(SuperBlocks);
return superBlocks.includes(entry.basename); return superBlocks.includes(filePath);
} }
}) })
).map(file => file.path); ).map(file => file.path);
expect(superBlockFiles.length).toBeGreaterThan(0);
superBlockFiles.forEach(file => { superBlockFiles.forEach(file => {
const fileContentJson = fs.readFileSync( const fileContentJson = fs.readFileSync(
`${clientStaticPath}/curriculum-data/${VERSION}/${file}`, `${clientStaticPath}/curriculum-data/${VERSION}/${file}`,
+62 -57
View File
@@ -2,63 +2,68 @@ const Joi = require('joi');
const blockSchema = Joi.object({}).keys({ const blockSchema = Joi.object({}).keys({
desc: Joi.array().min(1), desc: Joi.array().min(1),
challenges: Joi.object({}).keys({ challenges: Joi.object({})
name: Joi.string().required(), .keys({
isUpcomingChange: Joi.bool().required(), name: Joi.string().required(),
usesMultifileEditor: Joi.bool().optional(), isUpcomingChange: Joi.bool().required(),
hasEditableBoundaries: Joi.bool().optional(), usesMultifileEditor: Joi.bool().optional(),
dashedName: Joi.string().required(), hasEditableBoundaries: Joi.bool().optional(),
helpCategory: Joi.valid( dashedName: Joi.string().required(),
'JavaScript', helpCategory: Joi.valid(
'HTML-CSS', 'JavaScript',
'Python', 'HTML-CSS',
'Backend Development', 'Python',
'C-Sharp', 'Backend Development',
'English', 'C-Sharp',
'Odin', 'English',
'Euler', 'Odin',
'Rosetta' 'Euler',
).required(), 'Rosetta'
order: Joi.number().when('superBlock', { ).required(),
is: 'full-stack-developer', order: Joi.number().when('superBlock', {
then: Joi.forbidden(), is: 'full-stack-developer',
otherwise: Joi.required() then: Joi.forbidden(),
}), otherwise: Joi.required()
template: Joi.string().allow(''), }),
required: Joi.array(), template: Joi.string().allow(''),
superBlock: Joi.string().required(), required: Joi.array(),
blockLayout: Joi.valid( superBlock: Joi.string().required(),
'challenge-list', blockLayout: Joi.valid(
'challenge-grid', 'challenge-list',
'dialogue-grid', 'challenge-grid',
'link', 'dialogue-grid',
'project-list', 'link',
'legacy-challenge-list', 'project-list',
'legacy-link', 'legacy-challenge-list',
'legacy-challenge-grid' 'legacy-link',
).required(), 'legacy-challenge-grid'
blockType: Joi.valid( ).required(),
'lecture', blockType: Joi.valid(
'workshop', 'lecture',
'lab', 'workshop',
'review', 'lab',
'quiz', 'review',
'exam' 'quiz',
).when('superBlock', { 'exam'
is: 'full-stack-developer', ).when('superBlock', {
then: Joi.required(), is: 'full-stack-developer',
otherwise: Joi.optional() then: Joi.required(),
}), otherwise: Joi.optional()
challengeOrder: Joi.array().items( }),
Joi.object({}).keys({ challengeOrder: Joi.array()
id: Joi.string(), .items(
title: Joi.string() Joi.object({}).keys({
}) id: Joi.string(),
), title: Joi.string()
disableLoopProtectTests: Joi.boolean(), })
disableLoopProtectPreview: Joi.boolean(), )
superOrder: Joi.number() .min(1)
}) .required(),
disableLoopProtectTests: Joi.boolean(),
disableLoopProtectPreview: Joi.boolean(),
superOrder: Joi.number()
})
.required()
}); });
const subSchema = Joi.object({}).keys({ const subSchema = Joi.object({}).keys({
+62 -57
View File
@@ -7,63 +7,68 @@ const slugRE = new RegExp('^[a-z0-9-]+$');
const blockSchema = Joi.object().keys({ const blockSchema = Joi.object().keys({
intro: Joi.array().min(1), intro: Joi.array().min(1),
meta: Joi.object({}).keys({ meta: Joi.object({})
name: Joi.string().required(), .keys({
isUpcomingChange: Joi.bool().required(), name: Joi.string().required(),
usesMultifileEditor: Joi.bool().optional(), isUpcomingChange: Joi.bool().required(),
hasEditableBoundaries: Joi.bool().optional(), usesMultifileEditor: Joi.bool().optional(),
dashedName: Joi.string().required(), hasEditableBoundaries: Joi.bool().optional(),
helpCategory: Joi.valid( dashedName: Joi.string().required(),
'JavaScript', helpCategory: Joi.valid(
'HTML-CSS', 'JavaScript',
'Python', 'HTML-CSS',
'Backend Development', 'Python',
'C-Sharp', 'Backend Development',
'English', 'C-Sharp',
'Odin', 'English',
'Euler', 'Odin',
'Rosetta' 'Euler',
).required(), 'Rosetta'
order: Joi.number().when('superBlock', { ).required(),
is: chapterBasedSuperBlocks, order: Joi.number().when('superBlock', {
then: Joi.forbidden(), is: chapterBasedSuperBlocks,
otherwise: Joi.required() then: Joi.forbidden(),
}), otherwise: Joi.required()
template: Joi.string().allow(''), }),
required: Joi.array(), template: Joi.string().allow(''),
superBlock: Joi.string().required(), required: Joi.array(),
blockLayout: Joi.valid( superBlock: Joi.string().required(),
'challenge-list', blockLayout: Joi.valid(
'challenge-grid', 'challenge-list',
'dialogue-grid', 'challenge-grid',
'link', 'dialogue-grid',
'project-list', 'link',
'legacy-challenge-list', 'project-list',
'legacy-link', 'legacy-challenge-list',
'legacy-challenge-grid' 'legacy-link',
).required(), 'legacy-challenge-grid'
blockType: Joi.valid( ).required(),
'lecture', blockType: Joi.valid(
'workshop', 'lecture',
'lab', 'workshop',
'review', 'lab',
'quiz', 'review',
'exam' 'quiz',
).when('superBlock', { 'exam'
is: chapterBasedSuperBlocks, ).when('superBlock', {
then: Joi.required(), is: chapterBasedSuperBlocks,
otherwise: Joi.optional() then: Joi.required(),
}), otherwise: Joi.optional()
challengeOrder: Joi.array().items( }),
Joi.object({}).keys({ challengeOrder: Joi.array().items(
id: Joi.string(), Joi.object({})
title: Joi.string() .keys({
}) id: Joi.string(),
), title: Joi.string()
disableLoopProtectTests: Joi.boolean(), })
disableLoopProtectPreview: Joi.boolean(), .min(1)
superOrder: Joi.number() .required()
}) ),
disableLoopProtectTests: Joi.boolean(),
disableLoopProtectPreview: Joi.boolean(),
superOrder: Joi.number()
})
.required()
}); });
const blockBasedCurriculumSchema = Joi.object().pattern( const blockBasedCurriculumSchema = Joi.object().pattern(