From cea85c2acebe7f6cfe24266646725ba210dc23ab Mon Sep 17 00:00:00 2001 From: Louise Foussat Date: Sun, 7 Mar 2021 15:09:58 +0000 Subject: [PATCH] add happiness manager subject and tests --- js/tests/happiness-manager_test.mjs | 402 +++++++++++++++++++++++++++ subjects/happiness-manager/README.md | 52 ++++ 2 files changed, 454 insertions(+) create mode 100644 js/tests/happiness-manager_test.mjs create mode 100644 subjects/happiness-manager/README.md diff --git a/js/tests/happiness-manager_test.mjs b/js/tests/happiness-manager_test.mjs new file mode 100644 index 000000000..a77b86484 --- /dev/null +++ b/js/tests/happiness-manager_test.mjs @@ -0,0 +1,402 @@ +import * as cp from 'child_process' +import fs from 'fs/promises' +import { tmpdir } from 'os' +import { promisify } from 'util' +const mkdir = fs.mkdir +const writeFile = fs.writeFile +const readFile = fs.readFile + +const exec = promisify(cp.exec) + +export const tests = [] + +export const setup = async ({ path }) => { + const tmpPath = `${tmpdir()}/happiness-manager` + + await mkdir(tmpPath) + await mkdir(`${tmpPath}/guests`) + const run = async (folder, file) => { + const output = await exec( + `node ${path} ${tmpPath}/${folder} ${tmpPath}/${file}`, + ) + const fileContent = await readFile(`${tmpPath}/${file}`, 'utf8').catch( + (err) => (err.code === 'ENOENT' ? 'output file not found' : err), + ) + + return { + data: + fileContent === 'output file not found' + ? fileContent + : JSON.parse(fileContent), + stdout: output.stdout.trim(), + stderr: output.stderr.trim(), + } + } + const resetAnswersIn = async ({ folder }) => { + const dir = await fs.readdir(`${tmpPath}/${folder}`) + await Promise.all(dir.map((file) => fs.rm(`${tmpPath}/${folder}/${file}`))) + } + const createAnswers = (nb, elem) => [...Array(nb).keys()].map(() => elem) + const setAnswersIn = async ({ answers, folder }) => { + await resetAnswersIn({ folder }) + await Promise.all( + answers.map( + async (content, idx) => + await writeFile( + `${tmpPath}/${folder}/${idx}.json`, + JSON.stringify(content, null, '\t'), + 'utf8', + ), + ), + ) + } + + return { run, tmpPath, createAnswers, resetAnswersIn, setAnswersIn } +} + +tests.push(async ({ path, eq, ctx }) => { + // test with no vips (no {answer: yes}) + // no file should be created, a special message should appear in console + const answers = ctx.createAnswers(2, { answer: 'no' }) + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { stdout, data } = await ctx.run('guests', 'happy-list.json') + return eq( + { stdout, data }, + { stdout: 'No one is coming.', data: 'output file not found' }, + ) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { food: 'carnivores' } + // should create a list with burgers and potatoes + const answers = ctx.createAnswers(2, { answer: 'yes', food: 'carnivore' }) + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-carn-list.json') + return eq(data, { burgers: 2, potatoes: 2 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { food: 'fish' } + // should create a list with sardines and potatoes + const answers = [ + { answer: 'no', food: 'fish' }, + ...ctx.createAnswers(3, { answer: 'yes', food: 'fish' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-fish-list.json') + return eq(data, { potatoes: 3, sardines: 3 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { food: 'everything' } + // should create a list with kebabs and potatoes + const answers = [ + { answer: 'no', food: 'everything' }, + ...ctx.createAnswers(3, { answer: 'yes', food: 'everything' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-omni-list.json') + return eq(data, { potatoes: 3, kebabs: 3 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { drink: 'beer' } + // should create a list with 6-packs-beers and potatoes + const answers = [ + { answer: 'no', drink: 'beer' }, + ...ctx.createAnswers(1, { answer: 'yes', drink: 'beer' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-beer-list.json') + return eq(data, { potatoes: 1, '6-packs-beers': 1 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { drink: 'beer' } + // should create a list with 6-packs-beers and potatoes + const answers = [ + { answer: 'no', drink: 'beer' }, + ...ctx.createAnswers(6, { answer: 'yes', drink: 'beer' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-beer-pack-list.json') + return eq(data, { potatoes: 6, '6-packs-beers': 1 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { drink: 'wine' } + // should create a list with wine-bottles and potatoes + const answers = [ + ...ctx.createAnswers(3, { answer: 'no', drink: 'wine' }), + ...ctx.createAnswers(5, { answer: 'yes', drink: 'wine' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-wine-list.json') + return eq(data, { potatoes: 5, 'wine-bottles': 2 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { drink: 'wine' } + // should create a list with wine-bottles and potatoes + const answers = ctx.createAnswers(8, { answer: 'yes', drink: 'wine' }) + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-wine-bottle-list.json') + return eq(data, { potatoes: 8, 'wine-bottles': 2 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { drink: 'water' } + // should create a list with water-bottles and potatoes + const answers = [ + ...ctx.createAnswers(2, { answer: 'no', drink: 'water' }), + ...ctx.createAnswers(2, { answer: 'yes', drink: 'water' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-water-list.json') + return eq(data, { potatoes: 2, 'water-bottles': 1 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { drink: 'water' } + // should create a list with water-bottles and potatoes + const answers = ctx.createAnswers(7, { answer: 'yes', drink: 'water' }) + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-water-bottle-list.json') + return eq(data, { potatoes: 7, 'water-bottles': 2 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { drink: 'soft' } + // should create a list with soft-bottles and potatoes + const answers = [ + ...ctx.createAnswers(8, { answer: 'no', drink: 'soft' }), + ...ctx.createAnswers(12, { answer: 'yes', drink: 'soft' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-soft-list.json') + return eq(data, { potatoes: 12, 'soft-bottles': 3 }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { drink: 'soft' } + // should create a list with soft-bottles and potatoes + const answers = ctx.createAnswers(13, { answer: 'yes', drink: 'soft' }) + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-soft-bottle-list.json') + return eq(data, { potatoes: 13, 'soft-bottles': 4 }) +}) + +// tests with veggstuff +// 1) vegan but no veggie +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { food: 'vegan' } + // should create a list with eggplants, mushrooms, courgettes and potatoes + const answers = [ + ...ctx.createAnswers(2, { answer: 'no', food: 'vegan' }), + ...ctx.createAnswers(4, { answer: 'yes', food: 'vegan' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-vegan-list.json') + return eq(data, { + potatoes: 4, + mushrooms: 4, + eggplants: 2, + courgettes: 2, + hummus: 2, + }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { food: 'vegan' } + // should create a list with eggplants, mushrooms, hummus, courgettes and potatoes + const answers = ctx.createAnswers(6, { answer: 'yes', food: 'vegan' }) + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-vegan-list.json') + return eq(data, { + potatoes: 6, + mushrooms: 6, + eggplants: 2, + courgettes: 2, + hummus: 2, + }) +}) + +// 2) veggie but no vegan +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { food: 'veggie' } + // should create a list with eggplants, mushrooms, courgettes and potatoes + const answers = [ + ...ctx.createAnswers(2, { answer: 'no', food: 'veggie' }), + ...ctx.createAnswers(4, { answer: 'yes', food: 'veggie' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-veggie-list.json') + return eq(data, { + potatoes: 4, + mushrooms: 4, + eggplants: 2, + courgettes: 2, + hummus: 2, + }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { food: 'veggie' } + // should create a list with eggplants, mushrooms, hummus, courgettes and potatoes + const answers = ctx.createAnswers(6, { answer: 'yes', food: 'veggie' }) + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-veggie-list.json') + return eq(data, { + potatoes: 6, + mushrooms: 6, + eggplants: 2, + courgettes: 2, + hummus: 2, + }) +}) + +// 3) both +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { food: 'vegan' } and { food: 'veggie' } + // should create a list with eggplants, mushrooms, courgettes and potatoes + const answers = [ + ...ctx.createAnswers(4, { answer: 'yes', food: 'vegan' }), + ...ctx.createAnswers(2, { answer: 'yes', food: 'veggie' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-vegg-list.json') + return eq(data, { + potatoes: 6, + mushrooms: 6, + eggplants: 2, + courgettes: 2, + hummus: 2, + }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test when vips answer { food: 'vegan' } and { food: 'veggie' } + // should create a list with eggplants, mushrooms, hummus, courgettes and potatoes + const answers = [ + ...ctx.createAnswers(6, { answer: 'yes', food: 'vegan' }), + ...ctx.createAnswers(1, { answer: 'yes', food: 'veggie' }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'happy-vegg-list.json') + return eq(data, { + potatoes: 7, + mushrooms: 7, + eggplants: 3, + courgettes: 3, + hummus: 3, + }) +}) + +// test with existing file +tests.push(async ({ path, eq, ctx }) => { + // test with an existing file + // should add elems to the existing list + await writeFile( + `${ctx.tmpPath}/old-happy-list.json`, + JSON.stringify({ candies: 2000 }), + ) + const answers = ctx.createAnswers(1, { answer: 'yes', food: 'vegan' }) + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'old-happy-list.json') + return eq(data, { + candies: 2000, + potatoes: 1, + mushrooms: 1, + eggplants: 1, + courgettes: 1, + hummus: 1, + }) +}) + +tests.push(async ({ path, eq, ctx }) => { + // test with an existing file + // should replace elems in the existing list (if already there) + await writeFile( + `${ctx.tmpPath}/old-happy-list.json`, + JSON.stringify({ candies: 2000, potatoes: 32 }), + ) + const answers = ctx.createAnswers(1, { answer: 'yes', food: 'vegan' }) + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'old-happy-list.json') + return eq(data, { + candies: 2000, + potatoes: 1, + mushrooms: 1, + eggplants: 1, + courgettes: 1, + hummus: 1, + }) +}) + +// test with a little bit of everything +tests.push(async ({ path, eq, ctx }) => { + // test with mix of everything + await writeFile( + `${ctx.tmpPath}/party.json`, + JSON.stringify({ 'super-gift': 1, balloons: 100, 'flower-bouquet': 7 }), + ) + const answers = [ + ...ctx.createAnswers(7, { answer: 'no', food: 'vegan', drink: 'water' }), + ...ctx.createAnswers(2, { + answer: 'yes', + food: 'carnivore', + drink: 'soft', + }), + ...ctx.createAnswers(6, { answer: 'yes', food: 'vegan', drink: 'water' }), + ...ctx.createAnswers(2, { answer: 'yes', food: 'veggie', drink: 'water' }), + ...ctx.createAnswers(3, { answer: 'yes', food: 'veggie', drink: 'beer' }), + ...ctx.createAnswers(11, { answer: 'yes', food: 'fish', drink: 'wine' }), + ...ctx.createAnswers(4, { + answer: 'yes', + food: 'everything', + drink: 'beer', + }), + ] + await ctx.setAnswersIn({ folder: 'guests', answers }) + + const { data } = await ctx.run('guests', 'party.json') + return eq(data, { + 'super-gift': 1, + balloons: 100, + 'flower-bouquet': 7, + potatoes: 28, + mushrooms: 11, + eggplants: 4, + courgettes: 4, + hummus: 4, + sardines: 11, + burgers: 2, + kebabs: 4, + '6-packs-beers': 2, + 'wine-bottles': 3, + 'water-bottles': 2, + 'soft-bottles': 1, + }) +}) + +Object.freeze(tests) diff --git a/subjects/happiness-manager/README.md b/subjects/happiness-manager/README.md new file mode 100644 index 000000000..de24003d2 --- /dev/null +++ b/subjects/happiness-manager/README.md @@ -0,0 +1,52 @@ +## happiness-manager + +### Instructions + +<3 Your pleasure? 💖 That of others Ɛ> + +As you're smart, you asked every guest of the party to precise in their answer the kind of drink they would enjoy and the kind of food they would die for. + +Create a `happiness-manager.mjs` script that sort, in the vips, who wants to drink what and who wants to eat what and integrate that in your barbecue's shopping list! + +The script must: +- Take a folder as first argument (the `guest` folder) +- Take a file `.json` as second argument: + - If the file already exists, it will add the informations to it. + - If it doesn't, the script must handle the creation of the file. +- Handle case when no one answered yes to the invitation: + - `No one is coming.` has to appear in console. + - No file is updated/created. +- Handle cases when answers contains no "food" information, or no "drink" information +- Handle cases when no one has chosen a cotagory (for example: no one chose to drink softs). This category should not appear in the final list. + +You have to handle the info like this: +- Drinks: + - Beers: 1 pack / 6 vips (rounded up). Expected key: `6-packs-beers`. + - Water, wine, softs: 1 bottle / 4 vips in each category (rounded up). Expected keys: `wine-bottles`, `water-bottles`, `soft-bottles`. +- Food: + - Veggies and vegans: 1 eggplant, 1 courgette, 3 mushrooms and 1 hummus / 3 vips in these categories put together. Expected keys: `eggplants`, `mushrooms`, `hummus`, `courgettes`. + - Carnivores: 1 burger per person. Expected key: `burgers`. + - Fish lovers: 1 sardine per person. Expected key: `sardines`. + - Omnivores: 1 chicken+shrimps+pepper kebab / person. Expected key: `kebabs`. + - Bonus: you'll add 1 potatoe per person (all categories put together). Expected key: `potatoes`. + +The infos have to be formated like this in the `.json` file: +```json +{ + "key": 1 // according to actual number associated to the elem +} +``` + +### Notions + +- [`for...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) +- [`Array.prototype.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Map) +- [`Math.ceil()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil) +- [Node file system: `stats`](https://nodejs.org/api/fs.html#fs_fspromises_stat_path_options) +- [Node file system: `readdir`](https://nodejs.org/api/fs.html#fs_fspromises_readdir_path_options) +- [Node file system: `readFile`](https://nodejs.org/api/fs.html#fs_fspromises_readfile_path_options) +- [Node file system: `writeFile`](https://nodejs.org/api/fs.html#fs_fspromises_writefile_file_data_options) +- [`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) +- [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) +- [Node process: `exit`](https://nodejs.org/api/process.html#process_process_exit_code) +