From c05b3cd18e49216a9161ed1a0ee8b57af46baf7a Mon Sep 17 00:00:00 2001 From: Phil Tobias Date: Thu, 20 May 2021 18:55:28 -0700 Subject: [PATCH] add runners option for axe support --- config.js | 8 +- config/development.sample.json | 17 ++-- config/production.sample.json | 11 +-- config/test.sample.json | 9 ++- index.js | 1 + model/task.js | 142 +++++++++++++++++++-------------- package.json | 3 +- test/integration/startup.js | 6 +- test/unit/config.test.js | 35 +++++--- test/unit/csvToArray.test.js | 31 +++++++ utils/csvToArray.js | 9 +++ 11 files changed, 176 insertions(+), 96 deletions(-) create mode 100644 test/unit/csvToArray.test.js create mode 100644 utils/csvToArray.js diff --git a/config.js b/config.js index 318c1e9..806f173 100644 --- a/config.js +++ b/config.js @@ -15,15 +15,20 @@ 'use strict'; const fs = require('fs'); +const csvToArray = require('./utils/csvToArray'); const jsonPath = `./config/${process.env.NODE_ENV || 'development'}.json`; +const DEFAULT_RUNNER = 'htmlcs'; + if (fs.existsSync(jsonPath)) { const jsonConfig = require(jsonPath); + const runners = csvToArray(env('RUNNERS', jsonConfig.runners)); module.exports = { database: env('DATABASE', jsonConfig.database), host: env('HOST', jsonConfig.host), port: Number(env('PORT', jsonConfig.port)), + runners: runners.length ? runners : [DEFAULT_RUNNER], cron: env('CRON', jsonConfig.cron), chromeLaunchConfig: jsonConfig.chromeLaunchConfig || {}, numWorkers: jsonConfig.numWorkers || 2 @@ -33,6 +38,7 @@ if (fs.existsSync(jsonPath)) { database: env('DATABASE', 'mongodb://localhost/pa11y-webservice'), host: env('HOST', '0.0.0.0'), port: Number(env('PORT', '3000')), + runners: csvToArray(env('RUNNERS', DEFAULT_RUNNER)), cron: env('CRON', false), chromeLaunchConfig: {}, numWorkers: Number(env('NUM_WORKERS', '2')) @@ -41,5 +47,5 @@ if (fs.existsSync(jsonPath)) { function env(name, defaultValue) { const value = process.env[name]; - return (typeof value === 'string' ? value : defaultValue); + return typeof value === 'string' ? value : defaultValue; } diff --git a/config/development.sample.json b/config/development.sample.json index a1af1e8..5cc5d76 100644 --- a/config/development.sample.json +++ b/config/development.sample.json @@ -1,11 +1,10 @@ { - "database": "mongodb://localhost/pa11y-webservice-dev", - "host": "0.0.0.0", - "port": 3000, - "cron": "0 30 0 * * *", - "chromeLaunchConfig": { - "args": [ - "--no-sandbox" - ] - } + "database": "mongodb://localhost/pa11y-webservice-dev", + "host": "0.0.0.0", + "port": 3000, + "cron": "0 30 0 * * *", + "runners": "htmlcs", + "chromeLaunchConfig": { + "args": ["--no-sandbox"] + } } diff --git a/config/production.sample.json b/config/production.sample.json index 65d1e26..62db52b 100644 --- a/config/production.sample.json +++ b/config/production.sample.json @@ -1,7 +1,8 @@ { - "database": "mongodb://localhost/pa11y-webservice", - "host": "0.0.0.0", - "port": 3000, - "cron": "0 30 0 * * *", - "chromeLaunchConfig": {} + "database": "mongodb://localhost/pa11y-webservice", + "host": "0.0.0.0", + "port": 3000, + "cron": "0 30 0 * * *", + "runners": "htmlcs", + "chromeLaunchConfig": {} } diff --git a/config/test.sample.json b/config/test.sample.json index 37b77f6..fbf9f10 100644 --- a/config/test.sample.json +++ b/config/test.sample.json @@ -1,6 +1,7 @@ { - "database": "mongodb://localhost/pa11y-webservice-test", - "host": "0.0.0.0", - "port": 3000, - "chromeLaunchConfig": {} + "database": "mongodb://localhost/pa11y-webservice-test", + "host": "0.0.0.0", + "port": 3000, + "runners": "htmlcs", + "chromeLaunchConfig": {} } diff --git a/index.js b/index.js index 918d598..c5b0fb7 100644 --- a/index.js +++ b/index.js @@ -38,6 +38,7 @@ app(config, (error, initialisedApp) => { console.log(grey('mode: %s'), process.env.NODE_ENV); console.log(grey('uri: %s'), initialisedApp.server.info.uri); console.log(grey('database: %s'), dbConnectionString); + console.log(grey('runners: %s'), config.runners); console.log(grey('cron: %s'), config.cron); if (error) { diff --git a/model/task.js b/model/task.js index 6102702..8ce8abd 100644 --- a/model/task.js +++ b/model/task.js @@ -21,26 +21,30 @@ const {grey} = require('kleur'); const {ObjectID} = require('mongodb'); const pa11y = require('pa11y'); +const csvToArray = require('../utils/csvToArray'); // Task model module.exports = function(app, callback) { app.db.collection('tasks', function(errors, collection) { - collection.ensureIndex({ - name: 1, - url: 1, - standard: 1 - }, { - w: -1 - }); + collection.ensureIndex( + { + name: 1, + url: 1, + standard: 1 + }, + { + w: -1 + } + ); const model = { - collection: collection, // Create a task create: function(newTask) { newTask.headers = model.sanitizeHeaderInput(newTask.headers); - return collection.insert(newTask) + return collection + .insert(newTask) .then(result => { return model.prepareForOutput(result.ops[0]); }) @@ -79,7 +83,8 @@ module.exports = function(app, callback) { } // http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#findOne - return collection.findOne({_id: id}) + return collection + .findOne({_id: id}) .then(task => { return model.prepareForOutput(task); }) @@ -118,7 +123,8 @@ module.exports = function(app, callback) { taskEdits.headers = model.sanitizeHeaderInput(edits.headers); } - return collection.update({_id: id}, {$set: taskEdits}) + return collection + .update({_id: id}, {$set: taskEdits}) .then(updateCount => { if (updateCount < 1) { return 0; @@ -128,10 +134,9 @@ module.exports = function(app, callback) { date: now, comment: edits.comment || 'Edited task' }; - return model.addAnnotationById(idString, annotation) - .then(() => { - return updateCount; - }); + return model.addAnnotationById(idString, annotation).then(() => { + return updateCount; + }); }) .catch(error => { console.error(`model:task:editById failed, with id: ${id}`); @@ -142,20 +147,28 @@ module.exports = function(app, callback) { // Add an annotation to a task addAnnotationById: function(id, annotation) { - return model.getById(id) + return model + .getById(id) .then(task => { if (!task) { return 0; } id = new ObjectID(id); if (Array.isArray(task.annotations)) { - return collection.update({_id: id}, {$push: {annotations: annotation}}); + return collection.update( + {_id: id}, + {$push: {annotations: annotation}} + ); } - return collection.update({_id: id}, {$set: {annotations: [annotation]}}); - + return collection.update( + {_id: id}, + {$set: {annotations: [annotation]}} + ); }) .catch(error => { - console.error(`model:task:addAnnotationById failed, with id: ${id}`); + console.error( + `model:task:addAnnotationById failed, with id: ${id}` + ); console.error(error.message); return null; }); @@ -169,7 +182,8 @@ module.exports = function(app, callback) { console.error('ObjectID generation failed.', error.message); return null; } - return collection.deleteOne({_id: id}) + return collection + .deleteOne({_id: id}) .then(result => { return result ? result.deletedCount : null; }) @@ -182,45 +196,53 @@ module.exports = function(app, callback) { // Run a task by ID runById: function(id) { - return model.getById(id).then(async task => { - const pa11yOptions = { - standard: task.standard, - includeWarnings: true, - includeNotices: true, - timeout: (task.timeout || 30000), - wait: (task.wait || 0), - ignore: task.ignore, - actions: task.actions || [], - chromeLaunchConfig: app.config.chromeLaunchConfig || {}, - headers: task.headers || {}, - log: { - debug: model.pa11yLog(task.id), - error: model.pa11yLog(task.id), - info: model.pa11yLog(task.id), - log: model.pa11yLog(task.id) - } - }; - - // eslint-disable-next-line dot-notation - if (task.username && task.password && !pa11yOptions.headers['Authorization']) { - const encodedCredentials = Buffer.from(`${task.username}:${task.password}`) - .toString('base64'); + return model + .getById(id) + .then(async task => { + const pa11yOptions = { + standard: task.standard, + includeWarnings: true, + includeNotices: true, + timeout: task.timeout || 30000, + wait: task.wait || 0, + ignore: task.ignore, + actions: task.actions || [], + runners: csvToArray(app.config.runners), + chromeLaunchConfig: app.config.chromeLaunchConfig || {}, + headers: task.headers || {}, + log: { + debug: model.pa11yLog(task.id), + error: model.pa11yLog(task.id), + info: model.pa11yLog(task.id), + log: model.pa11yLog(task.id) + } + }; // eslint-disable-next-line dot-notation - pa11yOptions.headers['Authorization'] = `Basic ${encodedCredentials}`; - } + if ( + task.username && + task.password && + !pa11yOptions.headers.Authorization + ) { + const encodedCredentials = Buffer.from( + `${task.username}:${task.password}` + ).toString('base64'); - if (task.hideElements) { - pa11yOptions.hideElements = task.hideElements; - } + // eslint-disable-next-line dot-notation + pa11yOptions.headers.Authorization = `Basic ${encodedCredentials}`; + } - const pa11yResults = await pa11y(task.url, pa11yOptions); + if (task.hideElements) { + pa11yOptions.hideElements = task.hideElements; + } + + const pa11yResults = await pa11y(task.url, pa11yOptions); - const results = app.model.result.convertPa11y2Results(pa11yResults); - results.task = new ObjectID(task.id); - results.ignore = task.ignore; - return app.model.result.create(results); - }) + const results = app.model.result.convertPa11y2Results(pa11yResults); + results.task = new ObjectID(task.id); + results.ignore = task.ignore; + return app.model.result.create(results); + }) .catch(error => { console.error(`model:task:runById failed, with id: ${id}`); console.error(error.message); @@ -237,8 +259,8 @@ module.exports = function(app, callback) { id: task._id.toString(), name: task.name, url: task.url, - timeout: (task.timeout ? parseInt(task.timeout, 10) : 30000), - wait: (task.wait ? parseInt(task.wait, 10) : 0), + timeout: task.timeout ? parseInt(task.timeout, 10) : 30000, + wait: task.wait ? parseInt(task.wait, 10) : 0, standard: task.standard, ignore: task.ignore || [], actions: task.actions || [] @@ -260,7 +282,10 @@ module.exports = function(app, callback) { try { output.headers = JSON.parse(task.headers); } catch (error) { - console.error('Header input contains invalid JSON:', task.headers); + console.error( + 'Header input contains invalid JSON:', + task.headers + ); console.error(error.message); } } else { @@ -296,7 +321,6 @@ module.exports = function(app, callback) { console.log(grey(messageString)); }; } - }; callback(errors, model); }); diff --git a/package.json b/package.json index 5a94506..f83d644 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "data", "model", "route", - "task" + "task", + "utils" ] } diff --git a/test/integration/startup.js b/test/integration/startup.js index 568c825..99f1fe2 100644 --- a/test/integration/startup.js +++ b/test/integration/startup.js @@ -19,12 +19,12 @@ const config = require('../../config/test.json'); const app = require('../../app'); describe('pa11y-service startup', function() { - it('should start the service and call the callback', done => { const modifiedConfig = { database: config.database, host: config.host, - port: config.port + 10 + port: config.port + 10, + runners: config.runners }; app(modifiedConfig, (error, webservice) => { @@ -37,5 +37,3 @@ describe('pa11y-service startup', function() { }); }); }); - - diff --git a/test/unit/config.test.js b/test/unit/config.test.js index 22ac96e..e057c18 100644 --- a/test/unit/config.test.js +++ b/test/unit/config.test.js @@ -17,9 +17,9 @@ const fs = require('fs'); const path = require('path'); const assert = require('proclaim'); +const csvToArray = require('../../utils/csvToArray'); describe('config', () => { - const mockNodeEnv = 'mock'; let originalNodeEnv = 'test'; @@ -27,7 +27,8 @@ describe('config', () => { database: 'config-file-db', host: 'config-file-host', port: 1000, - cron: 'config-fille-cron', + runners: 'config-file-runners', + cron: 'config-file-cron', numWorkers: 2, chromeLaunchConfig: { field: 'value' @@ -45,8 +46,9 @@ describe('config', () => { }); describe('with a file', () => { - - const configFilePath = path.resolve(path.join(__dirname, '../../config/mock.json')); + const configFilePath = path.resolve( + path.join(__dirname, '../../config/mock.json') + ); before(done => { fs.writeFile(configFilePath, JSON.stringify(mockConfig), done); @@ -57,7 +59,6 @@ describe('config', () => { }); describe('and no environment variables', () => { - it('provides the config file', () => { delete require.cache[require.resolve('../../config')]; const config = require('../../config'); @@ -65,15 +66,17 @@ describe('config', () => { assert.strictEqual(config.database, mockConfig.database); assert.strictEqual(config.host, mockConfig.host); assert.strictEqual(config.port, mockConfig.port); + assert.deepEqual(config.runners, csvToArray(mockConfig.runners)); assert.strictEqual(config.cron, mockConfig.cron); assert.strictEqual(config.numWorkers, mockConfig.numWorkers); - assert.deepEqual(config.chromeLaunchConfig, mockConfig.chromeLaunchConfig); + assert.deepEqual( + config.chromeLaunchConfig, + mockConfig.chromeLaunchConfig + ); }); - }); describe('and some environment variables', () => { - beforeEach(() => { process.env.DATABASE = 'env-db'; process.env.PORT = '2000'; @@ -91,31 +94,36 @@ describe('config', () => { assert.strictEqual(config.database, 'env-db'); assert.strictEqual(config.host, mockConfig.host); assert.strictEqual(config.port, 2000); + assert.deepEqual(config.runners, csvToArray(mockConfig.runners)); assert.strictEqual(config.cron, mockConfig.cron); assert.strictEqual(config.numWorkers, mockConfig.numWorkers); - assert.deepEqual(config.chromeLaunchConfig, mockConfig.chromeLaunchConfig); + assert.deepEqual( + config.chromeLaunchConfig, + mockConfig.chromeLaunchConfig + ); }); }); }); describe('with no file', () => { - describe('and no environment variables', () => { - it('provides a default configuration', () => { delete require.cache[require.resolve('../../config')]; const config = require('../../config'); - assert.strictEqual(config.database, 'mongodb://localhost/pa11y-webservice'); + assert.strictEqual( + config.database, + 'mongodb://localhost/pa11y-webservice' + ); assert.strictEqual(config.host, '0.0.0.0'); assert.strictEqual(config.port, 3000); + assert.deepEqual(config.runners, ['htmlcs']); assert.strictEqual(config.cron, false); assert.deepEqual(config.chromeLaunchConfig, {}); }); }); describe('and environment variables', () => { - beforeEach(() => { process.env.DATABASE = 'env-db-2'; process.env.HOST = 'env-host-2'; @@ -138,6 +146,7 @@ describe('config', () => { assert.strictEqual(config.database, 'env-db-2'); assert.strictEqual(config.host, 'env-host-2'); assert.strictEqual(config.port, 3000); + assert.deepEqual(config.runners, ['htmlcs']); assert.strictEqual(config.cron, 'env-cron-2'); assert.strictEqual(config.numWorkers, 4); assert.deepEqual(config.chromeLaunchConfig, {}); diff --git a/test/unit/csvToArray.test.js b/test/unit/csvToArray.test.js new file mode 100644 index 0000000..b1ae431 --- /dev/null +++ b/test/unit/csvToArray.test.js @@ -0,0 +1,31 @@ +// This file is part of Pa11y Webservice. +// +// Pa11y Webservice is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Pa11y Webservice is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Pa11y Webservice. If not, see . +'use strict'; + +const assert = require('proclaim'); +const csvToArray = require('../../utils/csvToArray'); + +describe('csvToArray', () => { + it('returns empty array when no value passed', () => { + assert.deepEqual(csvToArray(), []); + }); + + it('returns array for comma separate values', () => { + assert.deepEqual(csvToArray('foo'), ['foo']); + assert.deepEqual(csvToArray('foo, bar'), ['foo', 'bar']); + assert.deepEqual(csvToArray('foo,bar'), ['foo', 'bar']); + assert.deepEqual(csvToArray('foo, bar, baz'), ['foo', 'bar', 'baz']); + }); +}); diff --git a/utils/csvToArray.js b/utils/csvToArray.js new file mode 100644 index 0000000..dc3a38f --- /dev/null +++ b/utils/csvToArray.js @@ -0,0 +1,9 @@ +function csvToArray(value) { + if (!value) { + return []; + } + + return value.split(/,/g).map(part => part.trim()); +} + +module.exports = csvToArray;