diff --git a/package-lock.json b/package-lock.json index 02e0d85..22732a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "fs-extra": "^11.1.0", "inquirer": "^8.2.5", "jest-junit": "^16.0.0", - "luxon": "^3.3.0" + "luxon": "^3.3.0", + "validator": "^13.12.0" }, "bin": { "cht-stock-monitoring-workflow": "main.js" @@ -12625,6 +12626,14 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vscode-json-languageservice": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-3.11.0.tgz", diff --git a/package.json b/package.json index 1a0fdd2..f518005 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "fs-extra": "^11.1.0", "inquirer": "^8.2.5", "jest-junit": "^16.0.0", - "luxon": "^3.3.0" + "luxon": "^3.3.0", + "validator": "^13.12.0" }, "config": { "commitizen": { diff --git a/src/add-item.js b/src/add-item.js index a2a30b0..aa068f5 100644 --- a/src/add-item.js +++ b/src/add-item.js @@ -2,6 +2,7 @@ const inquirer = require('inquirer'); const utils = require('./common'); const path = require('path'); const fs = require('fs-extra'); +const validator = require('validator'); /** * Config @@ -39,7 +40,7 @@ async function getItemConfig(configs) { if (!argv[14]){ return true; } - answers.form = argv[14]; + answers.form = validator.escape(argv[14]); return false; } } @@ -63,7 +64,7 @@ async function getItemConfig(configs) { if (!argv[15]){ return true; } - answers.isAlwaysCurrent = argv[15]; + answers.isAlwaysCurrent = validator.escape(argv[15]); return false; } } @@ -80,7 +81,7 @@ async function getItemConfig(configs) { if (!argv[16]){ return true; } - answers.reportedDate = argv[16]; + answers.reportedDate = validator.escape(argv[16]); return false; } } @@ -119,7 +120,7 @@ async function getItemConfig(configs) { if (!argv[17]){ return true; } - answers.item = argv[17]; + answers.item = validator.escape(argv[17]); return false; } }]); @@ -165,7 +166,7 @@ async function getItemConfig(configs) { if (!argv[17]){ return true; } - answers.name = argv[17]; + answers.name = validator.escape(argv[17]); return false; } }, @@ -178,26 +179,13 @@ async function getItemConfig(configs) { if (!argv[18]){ return true; } - let answer = {}; - argv[18].split(',').forEach(el => { - switch(language){ - case 'en': - answer = { - label: { - 'en': el - } - }; - break; - case 'fr': - answer = { - label: { - 'fr': el - } - }; - break; + const answer = { + label: { + 'en': validator.escape(argv[18].split(',')[0]), + 'fr': validator.escape(argv[18].split(',')[1]), } - }); - + }; + Object.assign(answers, answer); return false; } @@ -211,23 +199,12 @@ async function getItemConfig(configs) { if (!argv[19]){ return true; } - let answer = {}; - switch(language){ - case 'en': - answer = { - description: { - 'en': argv[19].split(',')[0] - } - }; - break; - case 'fr': - answer = { - description: { - 'fr': argv[19].split(',')[1] - } - }; - break; - } + const answer = { + description: { + 'en': validator.escape(argv[19].split(',')[0]), + 'fr': validator.escape(argv[19].split(',')[1]) + } + }; Object.assign(answers, answer); return false; @@ -246,7 +223,7 @@ async function getItemConfig(configs) { if (!argv[20]){ return true; } - answers.name = argv[20]; + answers.name = validator.escape(argv[20]); return false; } }, @@ -261,8 +238,8 @@ async function getItemConfig(configs) { } const answer = { label: { - 'en': argv[21].split(',')[0], - 'fr': argv[21].split(',')[1] + 'en': validator.escape(argv[21].split(',')[0]), + 'fr': validator.escape(argv[21].split(',')[1]) } }; Object.assign(answers, answer); @@ -279,7 +256,7 @@ async function getItemConfig(configs) { if (!argv[22]){ return true; } - answers.isInSet = argv[22]; + answers.isInSet = validator.escape(argv[22]); return false; } }, @@ -299,8 +276,8 @@ async function getItemConfig(configs) { const answer = { set:{ label: { - 'en': argv[23].split(',')[0], - 'fr': argv[23].split(',')[1] + 'en': validator.escape(argv[23].split(',')[0]), + 'fr': validator.escape(argv[23].split(',')[1]) } } }; @@ -319,7 +296,7 @@ async function getItemConfig(configs) { return true; } - answers.set.count = argv[24]; + answers.set.count = validator.escape(argv[24]); return false; } }, @@ -339,8 +316,8 @@ async function getItemConfig(configs) { const answer = { unit:{ label: { - 'en': argv[25].split(',')[0], - 'fr': argv[25].split(',')[1] + 'en': validator.escape(argv[25].split(',')[0]), + 'fr': validator.escape(argv[25].split(',')[1]) } } }; @@ -358,7 +335,7 @@ async function getItemConfig(configs) { if (!argv[26]){ return true; } - answers.warning_total = argv[26]; + answers.warning_total = validator.escape(argv[26]); return false; } }, @@ -371,7 +348,7 @@ async function getItemConfig(configs) { if (!argv[27]){ return true; } - answers.danger_total = argv[27]; + answers.danger_total = validator.escape(argv[27]); return false; } }, @@ -385,7 +362,7 @@ async function getItemConfig(configs) { if (!argv[28]){ return true; } - answers.max_total = argv[28]; + answers.max_total = validator.escape(argv[28]); return false; } } @@ -416,7 +393,7 @@ async function getItemConfig(configs) { if (!argv[29]){ return true; } - answers.deduction_type = argv[29]; + answers.deduction_type = validator.escape(argv[29]); return false; } } @@ -432,7 +409,7 @@ async function getItemConfig(configs) { if (!argv[30]){ return true; } - answers.formular = argv[30]; + answers.formular = validator.escape(argv[30]); return false; } } diff --git a/src/features/stock-out.js b/src/features/stock-out.js index 3fae84b..65c44d1 100644 --- a/src/features/stock-out.js +++ b/src/features/stock-out.js @@ -3,6 +3,8 @@ const path = require('path'); const fs = require('fs-extra'); const ExcelJS = require('exceljs'); const inquirer = require('inquirer'); +const validator = require('validator'); + const { getNoLabelsColums, getTranslations, getRowWithValueAtPosition, getNumberOfSteps, buildRowValues, getSheetGroupBeginEnd, getItemCount } = require('../common'); @@ -269,7 +271,7 @@ async function getStockOutConfigs({ if (!argv[5]){ return true; } - answers.form_name = argv[5]; + answers.form_name = validator.escape(argv[5]); return false; } }, @@ -292,7 +294,7 @@ async function getStockOutConfigs({ if (!argv[6]){ return true; } - answers.formular = argv[6]; + answers.formular = validator.escape(argv[6]); return false; } }, @@ -308,8 +310,8 @@ async function getStockOutConfigs({ } const answer = { title:{ - 'en': argv[7].split(',')[0], - 'fr': argv[7].split(',')[1] + 'en': validator.escape(argv[7].split(',')[0]), + 'fr': validator.escape(argv[7].split(',')[1]) } }; Object.assign(answers, answer); diff --git a/src/init.js b/src/init.js index 9ff85b9..3c43eee 100644 --- a/src/init.js +++ b/src/init.js @@ -2,6 +2,7 @@ const chalk = require('chalk'); const utils = require('./common'); const inquirer = require('inquirer'); const { getStockCountConfigs } = require('./features/stock-count'); +const validator = require('validator'); async function getInitConfigs() { const appSettings = utils.getAppSettings(); @@ -30,7 +31,7 @@ async function getInitConfigs() { when: function (answers) { const argv = process.argv; if (argv[3] === '2_levels') { - answers.monitoring_type = argv[3]; + answers.monitoring_type = validator.escape(argv[3]); return false; } return true; @@ -78,16 +79,16 @@ async function getInitConfigs() { case 1: answer = { 1: { - contact_type: argv[4], - role: argv[5] + contact_type: validator.escape(argv[4]), + role: validator.escape(argv[5]) } }; break; case 2: answer = { 2: { - contact_type: argv[6], - role: argv[7] + contact_type: validator.escape(argv[6]), + role: validator.escape(argv[7]) } }; break; @@ -112,16 +113,16 @@ async function getInitConfigs() { case 1: answer = { 1: { - contact_type: argv[4], - role: argv[5] + contact_type: validator.escape(argv[4]), + role: validator.escape(argv[5]) } }; break; case 2: answer = { 2: { - contact_type: argv[6], - role: argv[7] + contact_type: validator.escape(argv[6]), + role: validator.escape(argv[7]) } }; break; @@ -155,21 +156,21 @@ async function getInitConfigs() { case 1: answer = { 1: { - parent: argv[10], + parent: validator.escape(argv[10]), } }; break; case 2: answer = { 2: { - parent: argv[10], + parent: validator.escape(argv[10]), } }; break; case 3: answer = { 3: { - parent: argv[10], + parent: validator.escape(argv[10]), } }; break; diff --git a/test/mocks/mocks.js b/test/mocks/mocks.js index 38ad3b5..2fd6499 100644 --- a/test/mocks/mocks.js +++ b/test/mocks/mocks.js @@ -112,7 +112,41 @@ module.exports = { 'paracetamol_at_hand___unit', 'paracetamol_required___set', 'paracetamol_required___unit' - ] + ], + invalidCommandInitScenario: [ + 'test', + '2_levels', + 'c62_chw', + 'chw', + 'c52_supervisor', + 'supervisor', + 'Y', + 'stock_count', + '[{contact_type: \'c62_chw\', role: \'chw\', place_type: \'c60_chw_site\' },{contact_type: \'c52_supervisor\',role: \'supervisor\',place_type: \'c50_supervision_area\'}]', + 'action', + 'end_of_week', + ['Stock count', 'Stock count'], + 'patient_assessment_under_5', + 'Y', + 'now()', + 'malaria', + ['Category', 'Categorie'], + ['Category', 'Categorie'], + 'paracetamol', + ['Paracetamol', 'Paracetamole'], + 'Y', + ['Box of 8', 'Boite de 8'], + 8, + ['Tablet', 'Comprimes'], + 20, + 15, + 15, + 'by_user', + 0, + ], + invalidAddStockOutFeatureScenario: [ + 'minus', 'data', 'stock_out', 'stock_out', 'item_danger_qty', ['Stock Out', 'Stock Out'] + ], }, diff --git a/test/stock-count.spec.js b/test/stock-count.spec.js index 0d88463..eee8a0a 100644 --- a/test/stock-count.spec.js +++ b/test/stock-count.spec.js @@ -2,7 +2,7 @@ const { spawnSync } = require('child_process'); const path = require('path'); const fs = require('fs-extra'); -const { stockCountScenario } = require('./mocks/mocks'); +const { stockCountScenario, stockOutScenario } = require('./mocks/mocks'); const { setDirToprojectConfig, revertBackToProjectHome, @@ -48,7 +48,37 @@ describe('Create and update stock_count.xlsx and properties files', () => { revertBackToProjectHome(workingDir); }); - it('Add stock count integration test', async() => { + it('should not generate stock_count.xlsx and should return an error message', async() => { + const processDir = process.cwd(); + + // Check that stock count xform and properties files does not exist + for(const createdAppFormFile of createdAppFormFiles){ + expect(fs.existsSync(path.join(processDir, 'forms', 'app', createdAppFormFile))).toBe(false); + } + const invalidInputScenario = stockOutScenario.invalidCommandInitScenario; + const stockCountChildProcess = spawnSync('../../main.js', invalidInputScenario); + if(stockCountChildProcess.error) { + expect(stockCountChildProcess.stdout.toString()).toThrow(Error); + } + let message = stockCountChildProcess.stdout.toString().replace('\n',''); + expect(message).toEqual(`ERROR Unknown command ${invalidInputScenario[0]}`); + + const invalidStockOutScenario = stockOutScenario.invalidAddStockOutFeatureScenario; + const stockOutChildProcess = spawnSync('../../main.js', invalidStockOutScenario ); + if(stockOutChildProcess.error) { + expect(stockOutChildProcess.stdout.toString()).toThrow(Error); + } + message = stockOutChildProcess.stdout.toString().replace('\n',''); + expect(message).toEqual(`ERROR Unknown command ${invalidStockOutScenario[0]}`); + + // Check that stock count xform and properties files are not created + for(const createdAppFormFile of createdAppFormFiles){ + expect(fs.existsSync(path.join(processDir, 'forms', 'app', createdAppFormFile))).toBe(false); + } + + }); + + it('should generate and update the stock_count.xlsx with the init scenario', async() => { const processDir = process.cwd(); // Check that stock count xform and properties files does not exist for(const createdAppFormFile of createdAppFormFiles){ diff --git a/test/stock-out-feature-functions.spec.js b/test/stock-out-feature-functions.spec.js index 76392ea..b5810b0 100644 --- a/test/stock-out-feature-functions.spec.js +++ b/test/stock-out-feature-functions.spec.js @@ -73,16 +73,16 @@ describe('Testing functions in Stock out feature file', () => { expect(row).not.toEqual([]); expect(row.length).toBe(items.length); expect(row[0].length).toBe(7); - const rowData = row[0][0]; - expect(rowData.length).toBe(13); - expect(rowData[0]).toEqual('note'); - expect(rowData[1]).toContain(items[0].name); + const oneRow = row[0][0]; + expect(oneRow.length).toBe(13); + expect(oneRow[0]).toEqual('note'); + expect(oneRow[1]).toContain(items[0].name); }); /** Testing getStockOutConfigs function */ // Testing stock out config generation with correct item config - it('This should return stock out configurations based the item config provided ', async () => { + it('This should generate stock out configurations based the item config provided ', async () => { process.argv = ['node', '', '','', '' ,'stock_out', 'item_danger_qty', 'Stock Out, Rupture de Stock']; const configs = { form_name: 'stock_out', diff --git a/test/stock-out-integration.spec.js b/test/stock-out-integration.spec.js index 133515c..bcbc755 100644 --- a/test/stock-out-integration.spec.js +++ b/test/stock-out-integration.spec.js @@ -35,7 +35,7 @@ describe('Create and update stock_out.xlsx and properties files', () => { revertBackToProjectHome(workingDir); }); - it('Add stock out integration test', async() => { + it('should generate and update the stock_count.xlsx, stock_out.xlsx and their properties files whit the feature config provided', async() => { const processDir = process.cwd(); // Check that stock count and stock out xform and properties files does not exist diff --git a/test/test-utils.js b/test/test-utils.js index 69065f1..5c35c52 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -19,7 +19,12 @@ const revertBackToProjectHome = (projectHome) =>{ const cleanUp = (workingDir, fileNames) => { const processDir = path.join(workingDir,'test/project-config/'); for(const formFile of fileNames){ - fs.unlinkSync(path.join(processDir, 'forms', 'app', formFile)); + const filePath = path.join(processDir, 'forms', 'app', formFile); + fs.stat(filePath, (error) => { + if (!error) { + fs.unlinkSync(filePath); + } + }); } // Removing the stock monitoring init file and stock count file