From ab5c3d0f3d5dd014122054984223de5039894906 Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Tue, 21 Nov 2023 19:31:21 +0100 Subject: [PATCH] feat: warn mobile team if challenge schema updated (#52358) Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com> Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> --- .github/CODEOWNERS | 7 + .../challenge-schema.test.js.snap | 172 ++++++++++++++++++ curriculum/schema/challenge-schema.test.js | 13 ++ 3 files changed, 192 insertions(+) create mode 100644 curriculum/schema/__snapshots__/challenge-schema.test.js.snap create mode 100644 curriculum/schema/challenge-schema.test.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4266aeab421..37ffd108d77 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -46,3 +46,10 @@ pnpm-lock.yaml # i18n Quotes **/motivation.json @freeCodeCamp/staff @freeCodeCamp/i18n + +# ------------------------------------------------- +# Files that need attention from the mobile team +# ------------------------------------------------- + +/curriculum/schema/challenge-schema.js @freeCodeCamp/dev-team @freeCodeCamp/mobile +/client/src/redux/prop-types.ts @freeCodeCamp/dev-team @freeCodeCamp/mobile diff --git a/curriculum/schema/__snapshots__/challenge-schema.test.js.snap b/curriculum/schema/__snapshots__/challenge-schema.test.js.snap new file mode 100644 index 00000000000..653251f5e8f --- /dev/null +++ b/curriculum/schema/__snapshots__/challenge-schema.test.js.snap @@ -0,0 +1,172 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`challenge schema Notify mobile team BEFORE updating snapshot 1`] = ` +"const Joi = require('joi'); +Joi.objectId = require('joi-objectid')(Joi); + +const { challengeTypes } = require('../../shared/config/challenge-types'); + +const slugRE = new RegExp('^[a-z0-9-]+$'); +const slugWithSlashRE = new RegExp('^[a-z0-9-/]+$'); + +const fileJoi = Joi.object().keys({ + fileKey: Joi.string(), + ext: Joi.string(), + name: Joi.string(), + editableRegionBoundaries: [Joi.array().items(Joi.number())], + path: Joi.string(), + error: Joi.valid(null), + head: Joi.string().allow(''), + tail: Joi.string().allow(''), + seed: Joi.string().allow(''), + contents: Joi.string().allow(''), + id: Joi.string().allow(''), + history: Joi.array().items(Joi.string().allow('')) +}); + +const prerequisitesJoi = Joi.object().keys({ + id: Joi.objectId().required(), + title: Joi.string().required() +}); + +const schema = Joi.object() + .keys({ + audioPath: Joi.string(), + block: Joi.string().regex(slugRE).required(), + blockId: Joi.objectId(), + challengeOrder: Joi.number(), + removeComments: Joi.bool().required(), + certification: Joi.string().regex(slugRE), + challengeType: Joi.number().min(0).max(22).required(), + checksum: Joi.number(), + // TODO: require this only for normal challenges, not certs + dashedName: Joi.string().regex(slugRE), + description: Joi.when('challengeType', { + is: [ + challengeTypes.step, + challengeTypes.video, + challengeTypes.fillInTheBlank + ], + then: Joi.string().allow(''), + otherwise: Joi.string().required() + }), + disableLoopProtectTests: Joi.boolean().required(), + disableLoopProtectPreview: Joi.boolean().required(), + challengeFiles: Joi.array().items(fileJoi), + guideUrl: Joi.string().uri({ scheme: 'https' }), + hasEditableBoundaries: Joi.boolean(), + helpCategory: Joi.valid( + 'JavaScript', + 'HTML-CSS', + 'Python', + 'Backend Development', + 'C-Sharp' + ), + videoUrl: Joi.string().allow(''), + fillInTheBlank: Joi.object().keys({ + sentence: Joi.string().required(), + blanks: Joi.array() + .items( + Joi.object().keys({ + answer: Joi.string().required(), + feedback: Joi.string().allow(null) + }) + ) + .required() + }), + forumTopicId: Joi.number(), + id: Joi.objectId().required(), + instructions: Joi.string().allow(''), + isComingSoon: Joi.bool(), + isLocked: Joi.bool(), + isPrivate: Joi.bool(), + msTrophyId: Joi.when('challengeType', { + is: [challengeTypes.msTrophy], + then: Joi.string().required() + }), + notes: Joi.string().allow(''), + order: Joi.number(), + prerequisites: Joi.when('challengeType', { + is: [challengeTypes.exam], + then: Joi.array().items(prerequisitesJoi) + }), + // video challenges only: + videoId: Joi.when('challengeType', { + is: [challengeTypes.video, challengeTypes.dialogue], + then: Joi.string().required() + }), + videoLocaleIds: Joi.when('challengeType', { + is: challengeTypes.video, + then: Joi.object().keys({ + espanol: Joi.string(), + italian: Joi.string(), + portuguese: Joi.string() + }) + }), + bilibiliIds: Joi.when('challengeType', { + is: challengeTypes.video, + then: Joi.object().keys({ + aid: Joi.number().required(), + bvid: Joi.string().required(), + cid: Joi.number().required() + }) + }), + question: Joi.object().keys({ + text: Joi.string().required(), + answers: Joi.array() + .items( + Joi.object().keys({ + answer: Joi.string().required(), + feedback: Joi.string().allow(null) + }) + ) + .required(), + solution: Joi.number().required() + }), + required: Joi.array().items( + Joi.object().keys({ + link: Joi.string(), + raw: Joi.bool(), + src: Joi.string(), + crossDomain: Joi.bool() + }) + ), + assignments: Joi.when('challengeType', { + is: challengeTypes.dialogue, + then: Joi.array().items(Joi.string()).required(), + otherwise: Joi.array().items(Joi.string()) + }), + solutions: Joi.array().items(Joi.array().items(fileJoi).min(1)), + superBlock: Joi.string().regex(slugWithSlashRE), + superOrder: Joi.number(), + suborder: Joi.number(), + tests: Joi.array().items( + // public challenges + Joi.object().keys({ + id: Joi.string().allow(''), + text: Joi.string().required(), + testString: Joi.string().allow('').required() + }), + // our tests used in certification verification + Joi.object().keys({ + id: Joi.string().required(), + title: Joi.string().required() + }) + ), + template: Joi.string().allow(''), + time: Joi.string().allow(''), + title: Joi.string().required(), + translationPending: Joi.bool().required(), + url: Joi.when('challengeType', { + is: [challengeTypes.codeAllyPractice, challengeTypes.codeAllyCert], + then: Joi.string().required() + }), + usesMultifileEditor: Joi.boolean() + }) + .xor('helpCategory', 'isPrivate'); + +exports.challengeSchemaValidator = () => { + return challenge => schema.validate(challenge); +}; +" +`; diff --git a/curriculum/schema/challenge-schema.test.js b/curriculum/schema/challenge-schema.test.js new file mode 100644 index 00000000000..36c11d2def8 --- /dev/null +++ b/curriculum/schema/challenge-schema.test.js @@ -0,0 +1,13 @@ +const { readFileSync } = require('fs'); +const path = require('path'); + +describe('challenge schema', () => { + it('Notify mobile team BEFORE updating snapshot', () => { + const schemaFile = readFileSync( + path.resolve(__dirname, './challenge-schema.js'), + 'utf8' + ); + + expect(schemaFile).toMatchSnapshot(); + }); +});