diff --git a/.gitignore b/.gitignore index b5f7564..aa91d6a 100755 --- a/.gitignore +++ b/.gitignore @@ -142,4 +142,4 @@ public/ .topcoderrc # package-lock.json vary while installing on different operating system -package-lock.json \ No newline at end of file +package-lock.json diff --git a/bin/topcoder-cli.js b/bin/topcoder-cli.js index debb00d..6ef20df 100644 --- a/bin/topcoder-cli.js +++ b/bin/topcoder-cli.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const program = require('commander') +const { Command } = require('commander') const submissionHandler = require('../src/commands/submit') const payHandler = require('../src/commands/pay') const configHandler = require('../src/commands/config') @@ -8,6 +8,68 @@ const fetchSubmissionHandler = require('../src/commands/fetchSubmissions') const fetchArtifactsHandler = require('../src/commands/fetchArtifacts') const logger = require('../src/common/logger') +const docs = { + submit: `\nEither use CLI parameters or Create a file .topcoderrc in JSON ` + + `format with below details\n` + + `{\n` + + ` "memberId": "",\n` + + ` "password": "",\n` + + ` "m2m": {\n` + + ` "client_id": "",\n` + + ` "client_secret": ""\n` + + ` }\n` + + `}\n` + + `and execute command \`topcoder submit\` to submit the contents of ` + + `current working directory except .topcoderrc file to the challenge.\n` + + `You'd need either the m2m config or the username and password, but ` + + `not both.`, + 'fetch-submissions': `\nUse CLI parameters or create a file .topcoderrc in JSON format with below details\n` + + `{\n` + + ` "memberId": "",\n` + + ` "password": "",\n` + + ` "m2m": {\n` + + ` "client_id": "",\n` + + ` "client_secret": ""\n` + + ` }\n` + + `}\n` + + `and execute command \`topcoder fetch-submissions\` to fetch submissions ` + + `for a challenge and save them.\n` + + `You may specify the m2m config or the username and password config, ` + + `but not both.\n` + + `If the submissionId parameter is provided, you must not provide the ` + + `memberId or the latest parameters.\n` + + `The challengeId parameter is always required.`, + 'fetch-artifacts': `\nUse CLI parameters or create a file .topcoderrc in JSON format ` + + `with below details\n` + + `{\n` + + ` "submissionId": "",\n` + + ` "legacySubmissionId": "",\n` + + ` "username": "",\n` + + ` "password": "",\n` + + ` "m2m": {\n` + + ` "client_id": "",\n` + + ` "client_secret": ""\n` + + ` }\n` + + `}\n` + + `and execute command \`topcoder fetch-artifacts\` to fetch submissions for` + + ` a challenge and save them.\n` + + `You may specify the m2m config or the username and password config, ` + + `but not both.\n` + + `If the submissionId parameter is provided, you must not provide the the ` + + `legacySubmissionId parameters, and vice-versa.` + +} + +const program = new Command() + // Overall help text which will be displayed after usage information program.on('--help', () => { console.log('\nTopcoder CLI to interact with Topcoder systems\n') @@ -30,26 +92,7 @@ program ) .option('--dev', 'Points to Topcoder development environment') .on('--help', () => { - console.log( - `\nEither use CLI parameters or Create a file .topcoderrc in JSON ` + - `format with below details\n` + - `{\n` + - ` "memberId": "",\n` + - ` "password": "",\n` + - ` "m2m": {\n` + - ` "client_id": "",\n` + - ` "client_secret": ""\n` + - ` }\n` + - `}\n` + - `and execute command \`topcoder submit\` to submit the contents of ` + - `current working directory except .topcoderrc file to the challenge.\n` + - `You'd need either the m2m config or the username and password, but ` + - `not both.` - ) + console.log(docs.submit) }) .action(async args => { try { @@ -82,28 +125,7 @@ program .option('-l, --latest', 'fetch only the latest submission of each member') .option('--dev', 'Points to Topcoder development environment') .on('--help', () => { - console.log( - `\nUse CLI parameters or create a file .topcoderrc in JSON format with below details\n` + - `{\n` + - ` "memberId": "",\n` + - ` "password": "",\n` + - ` "m2m": {\n` + - ` "client_id": "",\n` + - ` "client_secret": ""\n` + - ` }\n` + - `}\n` + - `and execute command \`topcoder fetch-submissions\` to fetch submissions ` + - `for a challenge and save them.\n` + - `You may specify the m2m config or the username and password config, ` + - `but not both.\n` + - `If the submissionId parameter is provided, you must not provide the ` + - `memberId or the latest parameters.\n` + - `The challengeId parameter is always required.` - ) + console.log(docs['fetch-submissions']) }) .action(async args => { try { @@ -132,24 +154,7 @@ program .option('--dev', 'Points to Topcoder development environment') .on('--help', () => { console.log( - `\nUse CLI parameters or create a file .topcoderrc in JSON format ` + - `with below details\n` + - `{\n` + - ` "submissionId": "",\n` + - ` "legacySubmissionId": "",\n` + - ` "username": "",\n` + - ` "password": "",\n` + - ` "m2m": {\n` + - ` "client_id": "",\n` + - ` "client_secret": ""\n` + - ` }\n` + - `}\n` + - `and execute command \`topcoder fetch-artifacts\` to fetch submissions for` + - ` a challenge and save them.\n` + - `You may specify the m2m config or the username and password config, ` + - `but not both.\n` + - `If the submissionId parameter is provided, you must not provide the the ` + - `legacySubmissionId parameters, and vice-versa.` + docs['fetch-artifacts'] ) }) .action(async args => { @@ -186,7 +191,7 @@ program if (args.dev) { process.env.NODE_ENV = 'dev' } - payHandler.handleCommand(args) + payHandler.handleCommand(program.args) }) // error on unknown commands @@ -199,9 +204,16 @@ program.on('command:*', function () { process.exit(1) }) -program.parse(process.argv) +/* istanbul ignore next */ +if (!module.parent) { + program.parse(process.argv) + // If the CLI is invoked without any command, display help + if (process.argv.length < 3) { + program.help() + } +} -// If the CLI is invoked without any command, display help -if (process.argv.length < 3) { - program.help() +module.exports = { + program, + docs } diff --git a/config/prod.js b/config/prod.js index b471fbc..70fa869 100644 --- a/config/prod.js +++ b/config/prod.js @@ -8,7 +8,7 @@ module.exports = { process.env.TC_MEMBERS_API || 'https://api.topcoder.com/v3/members', SUBMISSION_API_URL: process.env.TEST_SUBMISSION_API_URL || - 'https://api.topcoder.com/v5/submissions', + 'https://api.topcoder.com/v5', AUTH0_URL: process.env.AUTH0_URL || 'https://topcoder.auth0.com/oauth/token', TC_AUTHN_URL: process.env.TC_AUTHN_URL || 'https://topcoder.auth0.com/oauth/ro', diff --git a/docs/Development.md b/docs/Development.md index a2c6326..befec7f 100755 --- a/docs/Development.md +++ b/docs/Development.md @@ -19,6 +19,14 @@ The following parameters can be set in config files or in env variables: | TC_CLIENT_V2CONNECTION | CLIENT_V2CONNECTION | TC-User-Database | TC client connection protocol | | AUTH0_AUDIENCE | AUTH0_AUDIENCE | https://m2m.topcoder.com/ | AUTH0 Audience (For M2M) | +# Configuration for Test +Configuration for test is at `test/common/testConfig.js`. +The following parameters can be set in config files or in env variables: + +| Property | Environment varible | Default value | Description | +| --- | --- | --- | --- | +| WAIT_TIME | WAIT_TIME | 500 | Waiting time for the CLI tool to process subcommands. Increase the value if needed | + # Publish the package to npm - Create a npm account on https://www.npmjs.com/signup if you don't have one. - Use the account to sign in via cli: `npm login` diff --git a/package.json b/package.json index 71e9399..38b4221 100755 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "lint": "standard", "lint:fix": "standard --fix", "format": "prettier-standard \"src/**/*.js\" \"bin/**/*.js\" \"test/**/*.js\" \"config/**/*.js\"", - "test": "mocha --require test/prepare.js -t 20000 test/unit.test.js --exit", + "test": "mocha --require test/prepare.js -t 20000 test/*.test.js --exit", "test:cov": "nyc --reporter=html --reporter=text npm test" }, "dependencies": { @@ -23,7 +23,9 @@ "lodash": "^4.17.15", "moment": "^2.24.0", "prompts": "^2.2.1", + "stream-mock": "^2.0.5", "superagent": "^5.0.5", + "uuid": "^3.3.3", "winston": "^3.2.1" }, "bin": { @@ -41,17 +43,24 @@ "homepage": "https://topcoder-platform.github.io/topcoder-cli/", "devDependencies": { "chai": "^4.2.0", + "delay": "^4.3.0", "mocha": "^6.1.4", "mocha-prepare": "^0.1.0", + "mock-require": "^3.0.3", "nock": "^10.0.6", "nyc": "^14.1.1", "prettier-standard": "^16.0.0", - "sinon": "^7.5.0", "standard": "^13.1.0" }, "standard": { "env": { "mocha": true } + }, + "nyc": { + "exclude": [ + "test/**", + "src/common/logger.js" + ] } } diff --git a/src/commands/pay.js b/src/commands/pay.js index b5457ce..a4eaf69 100644 --- a/src/commands/pay.js +++ b/src/commands/pay.js @@ -1,11 +1,12 @@ const prompts = require('prompts') +const logger = require('../common/logger') /** * Handles the "pay" command * @param {Array} args Arguments */ function handleCommand (args) { - const options = args[args.length - 1].opts() + const options = args[0].opts() const challengeDetails = [ { type: 'text', @@ -41,7 +42,7 @@ function handleCommand (args) { const response = await prompts(challengeDetails) // respose + options.copilot (copilot payment money) contains total // information needed to send request to API. - console.log(response) + logger.info(response) // => response => { username, age, about } } promptQuestions() diff --git a/src/common/helper.js b/src/common/helper.js index a9bd21a..3005ceb 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -5,8 +5,6 @@ const submissionApi = require('@topcoder-platform/topcoder-submission-api-wrappe const logger = require('./logger') const configService = require('../services/configService') -let submissionApiClient = null - const defaultAuthSchema = Joi.object({ username: Joi.string(), password: Joi.string(), @@ -87,9 +85,6 @@ async function readFromRCFile (filename, cliParams, schema, validCLIParams) { } function getAPIClient (userName, password, m2m) { - if (submissionApiClient) { - return submissionApiClient - } const config = require('../config')() let clientConfig if (userName && password) { @@ -113,7 +108,7 @@ function getAPIClient (userName, password, m2m) { clientConfig.AUTH0_CLIENT_ID = m2m.client_id clientConfig.AUTH0_CLIENT_SECRET = m2m.client_secret } - submissionApiClient = submissionApi(clientConfig) + const submissionApiClient = submissionApi(clientConfig) return submissionApiClient } diff --git a/src/services/configService.js b/src/services/configService.js index be07e05..1f115ae 100644 --- a/src/services/configService.js +++ b/src/services/configService.js @@ -51,7 +51,7 @@ async function addToConfigFile (key, value) { let config = ini.parse('') try { - config = readFromConfigFile() + config = await readFromConfigFile() } catch (error) { // catching .tcconfig file not found error here. logger.info('Topcoder config file not found, creating file in' + homedir) @@ -66,19 +66,15 @@ async function addToConfigFile (key, value) { * @param {String} keyToBeDeleted Property key (to be deleted) */ async function deleteFromConfigFile (keyToBeDeleted) { - const config = readFromConfigFile() - let isDeleted = false - - isDeleted = _.unset(config, keyToBeDeleted) - - if (isDeleted) { - await fs.writeFile(configPath, ini.stringify(config)) - logger.info( - `${keyToBeDeleted} is removed from the config file successfully.` - ) - } else { - throw new Error(`${keyToBeDeleted} is not found in the config fle.`) + const config = await readFromConfigFile() + if (_.isUndefined(_.get(config, keyToBeDeleted))) { + throw new Error(`${keyToBeDeleted} is not found in the config file.`) } + _.unset(config, keyToBeDeleted) + await fs.writeFile(configPath, ini.stringify(config)) + logger.info( + `${keyToBeDeleted} is removed from the config file successfully.` + ) } module.exports = { diff --git a/test/cli.test.js b/test/cli.test.js new file mode 100644 index 0000000..6037ddd --- /dev/null +++ b/test/cli.test.js @@ -0,0 +1,87 @@ +/* + * Test CLI functionalities. + */ +const delay = require('delay') +const chai = require('chai') +const _ = require('lodash') + +const { program, docs } = require('../bin/topcoder-cli') +const testHelper = require('./common/testHelper') +const testConfig = require('./common/testConfig') + +const localTestData = { + argsWithHelp: testHelper.buildArgs(undefined, { help: true }), + argsForCommandSubmit: testHelper.buildArgs('submit', { help: true }), + argsForCommandFetchSubmissions: testHelper.buildArgs('fetch-submissions', { help: true }), + argsForCommandFetchArtifacts: testHelper.buildArgs('fetch-artifacts', { help: true }), + argsWithUnknownCommand: testHelper.buildArgs('unknown-command') +} + +describe('CLI Test', async function () { + const mocks = {} + let messages = [] + let errorMessages = [] + + before(async function () { + const logInfo = console.log + mocks.interceptInfo = testHelper.mockFunction( + console, 'log', (message) => { + messages.push(message) + logInfo(message) + } + ) + const logErrorInfo = console.error + mocks.interceptErrorInfo = testHelper.mockFunction( + console, 'error', (message) => { + errorMessages.push(message) + logErrorInfo(message) + } + ) + // we need to stub the process.exit function otherwise the test will be + // terminated after the program outputs help info. + mocks.showDocsAndKeepAlive = testHelper.mockFunction( + process, 'exit', () => {} + ) + }) + + afterEach(async function () { + messages = [] + errorMessages = [] + }) + + after(async function () { + for (const mock of Object.values(mocks)) { + mock.restore() + } + }) + + it('It should show general help info', async function () { + program.parse(localTestData.argsWithHelp) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(messages)).to.include('Topcoder CLI to interact with Topcoder systems') + }) + + it('It should show help documentation for command submit', async function () { + program.parse(localTestData.argsForCommandSubmit) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(messages)).to.equal(docs.submit) + }) + + it('It should show help documentation for command fetch-submissions', async function () { + program.parse(localTestData.argsForCommandFetchSubmissions) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(messages)).to.equal(docs['fetch-submissions']) + }) + + it('It should show help documentation for command fetch-artifacts', async function () { + program.parse(localTestData.argsForCommandFetchArtifacts) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(messages)).to.equal(docs['fetch-artifacts']) + }) + + it('failure - It should handle unknown commands', async function () { + program.parse(localTestData.argsWithUnknownCommand) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('Invalid command:') + }) +}) diff --git a/test/common/submissions.json b/test/common/submissions.json new file mode 100644 index 0000000..e775f52 --- /dev/null +++ b/test/common/submissions.json @@ -0,0 +1,634 @@ +[ + { + "updatedBy": "aaron2017", + "created": "2019-12-06T07:01:43.945Z", + "legacySubmissionId": 336926, + "isFileSubmission": false, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-submissions/b8ee3343-f3cf-4f39-9618-04a70b900a68.zip", + "challengeId": 30109319, + "createdBy": "aaron2017", + "review": [ + { + "score": 100, + "updatedBy": "mL4037GVeLQOvb8TgU4KW38mlevpfp4y@clients", + "reviewerId": "b7a77c98-81ae-4511-b9c7-af0301a4a145", + "submissionId": "b8ee3343-f3cf-4f39-9618-04a70b900a68", + "createdBy": "mL4037GVeLQOvb8TgU4KW38mlevpfp4y@clients", + "created": "2019-12-06T07:05:00.538Z", + "scoreCardId": 30001850, + "typeId": "55bbb17d-aac2-45a6-89c3-a8d102863d05", + "id": "42efcd17-20a9-4ff6-92c6-d944e5f7b0bf", + "updated": "2019-12-06T07:05:00.538Z", + "status": "completed" + } + ], + "id": "b8ee3343-f3cf-4f39-9618-04a70b900a68", + "submissionPhaseId": 1219343, + "updated": "2019-12-06T07:01:43.945Z", + "fileType": "zip", + "memberId": 40687077 + }, + { + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-02T01:21:12.844Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/4d38573f-404c-4c2f-90b1-a05ccd3fe860.zip", + "challengeId": 30095545, + "legacySubmissionId": "208870", + "filename": "8547899.zip", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "a29786fa-bf42-4709-b636-0cc2b64c9827", + "submissionId": "4d38573f-404c-4c2f-90b1-a05ccd3fe860", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-02T01:21:15.839Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "d234cbe9-ced9-4507-848e-d94e76419815", + "updated": "2019-12-02T01:21:15.839Z", + "status": "completed" + } + ], + "id": "4d38573f-404c-4c2f-90b1-a05ccd3fe860", + "submissionPhaseId": null, + "updated": "2019-12-02T01:21:12.844Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-02T01:18:40.879Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/bacacaa1-056f-4438-bf02-c9efe6334b2f.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "17e66d97-4742-4b63-9589-fe120ec2b984", + "submissionId": "bacacaa1-056f-4438-bf02-c9efe6334b2f", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-02T01:18:44.537Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "e551d414-8bbb-458a-a884-2ee9fb604cf4", + "updated": "2019-12-02T01:18:44.537Z", + "status": "completed" + } + ], + "id": "bacacaa1-056f-4438-bf02-c9efe6334b2f", + "submissionPhaseId": null, + "updated": "2019-12-02T01:18:40.879Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-01T22:27:46.210Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/845efeb7-1e8f-4011-89aa-fe26182229f9.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "0bd54b07-c5f0-4e02-8783-c582e0dcf9ca", + "submissionId": "845efeb7-1e8f-4011-89aa-fe26182229f9", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-01T22:27:49.625Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "69d79baa-ad11-4b1a-af3c-15ea2a1125d2", + "updated": "2019-12-01T22:27:49.625Z", + "status": "completed" + } + ], + "id": "845efeb7-1e8f-4011-89aa-fe26182229f9", + "submissionPhaseId": null, + "updated": "2019-12-01T22:27:46.210Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-12-01T22:27:28.595Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/1f1f3fac-c4ee-45c3-8ed8-51bb5f9c44fb.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "24aa6b77-e8ae-498e-b916-877419f80a9d", + "submissionId": "1f1f3fac-c4ee-45c3-8ed8-51bb5f9c44fb", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-01T22:27:31.971Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "b23cd5af-2c32-4f28-b07b-c7b0aa1e5bb1", + "updated": "2019-12-01T22:27:31.971Z", + "status": "completed" + } + ], + "id": "1f1f3fac-c4ee-45c3-8ed8-51bb5f9c44fb", + "submissionPhaseId": null, + "updated": "2019-12-01T22:27:28.595Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-12-01T22:26:37.023Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/3b9040ff-faa2-4169-b313-e663c95f688d.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "8a2d38c3-2341-4158-93b1-3002b2468539", + "submissionId": "3b9040ff-faa2-4169-b313-e663c95f688d", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-01T22:26:40.059Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "20d74dd8-32d5-42fe-bfa0-a16f13092722", + "updated": "2019-12-01T22:26:40.059Z", + "status": "completed" + } + ], + "id": "3b9040ff-faa2-4169-b313-e663c95f688d", + "submissionPhaseId": null, + "updated": "2019-12-01T22:26:37.023Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-12-01T22:20:58.417Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/f1a325d8-d7ae-4e8e-b35d-9af0b08c4a6b.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "98b7ff0d-1686-47d6-a1be-43b97e4a0bfa", + "submissionId": "f1a325d8-d7ae-4e8e-b35d-9af0b08c4a6b", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-01T22:21:02.112Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "cdd715df-bb65-4a72-bf21-9d9e5374a279", + "updated": "2019-12-01T22:21:02.112Z", + "status": "completed" + } + ], + "id": "f1a325d8-d7ae-4e8e-b35d-9af0b08c4a6b", + "submissionPhaseId": null, + "updated": "2019-12-01T22:20:58.417Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-12-01T21:57:28.396Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/c59f1eb8-8f65-467c-96e4-905c1d46ede2.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "75625efe-5bba-462d-90e3-ca27adf03458", + "submissionId": "c59f1eb8-8f65-467c-96e4-905c1d46ede2", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-01T21:57:32.286Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "61c65321-4894-45a6-ac6c-d73855cddf51", + "updated": "2019-12-01T21:57:32.286Z", + "status": "completed" + } + ], + "id": "c59f1eb8-8f65-467c-96e4-905c1d46ede2", + "submissionPhaseId": null, + "updated": "2019-12-01T21:57:28.396Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-12-01T17:11:57.626Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/613fe134-f6b6-424f-bbb2-5462e35b9e58.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "84cde41e-b8d3-4e8a-89eb-6a8bb9f69607", + "submissionId": "613fe134-f6b6-424f-bbb2-5462e35b9e58", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-12-01T17:12:03.509Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "ada53926-d379-455e-8a85-e0220eeb97f4", + "updated": "2019-12-01T17:12:03.509Z", + "status": "completed" + } + ], + "id": "613fe134-f6b6-424f-bbb2-5462e35b9e58", + "submissionPhaseId": null, + "updated": "2019-12-01T17:11:57.626Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-11-28T14:50:09.217Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/da8469fb-ccd3-4e87-bb8d-b2df2a1771c5.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "f6710095-ba40-499b-a691-1d158e5f69e6", + "submissionId": "da8469fb-ccd3-4e87-bb8d-b2df2a1771c5", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-11-28T14:50:13.395Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "d9e1748a-98f7-4465-b86f-749a689913a9", + "updated": "2019-11-28T14:50:13.395Z", + "status": "completed" + } + ], + "id": "da8469fb-ccd3-4e87-bb8d-b2df2a1771c5", + "submissionPhaseId": null, + "updated": "2019-11-28T14:50:09.217Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-10-08T03:15:23.194Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/52577108-cff5-4a27-8956-5b00d12f66f3.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "91950738-13d1-432c-9bb8-b262f6de8024", + "submissionId": "52577108-cff5-4a27-8956-5b00d12f66f3", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-08T03:15:27.109Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "afe5dcea-91bc-41dc-a657-457a38b2ec0d", + "updated": "2019-10-08T03:15:27.109Z", + "status": "completed" + } + ], + "id": "52577108-cff5-4a27-8956-5b00d12f66f3", + "submissionPhaseId": null, + "updated": "2019-10-08T03:15:23.194Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-10-08T02:10:19.078Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/658c5c61-03c3-4bec-852c-5ad83a20b418.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "fa22f273-38a8-41ce-b48f-ab33472d8afb", + "submissionId": "658c5c61-03c3-4bec-852c-5ad83a20b418", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-08T02:10:21.575Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "f4f700bb-3308-4488-8bea-22ff338a498e", + "updated": "2019-10-08T02:10:21.575Z", + "status": "completed" + } + ], + "id": "658c5c61-03c3-4bec-852c-5ad83a20b418", + "submissionPhaseId": null, + "updated": "2019-10-08T02:10:19.078Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-10-06T23:01:34.310Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/2f984898-de0a-442a-8eed-8d7ce8c9e6a4.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "129cbdb9-324d-499d-9f14-a4056f99b665", + "submissionId": "2f984898-de0a-442a-8eed-8d7ce8c9e6a4", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-06T23:01:38.282Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "40c2ced8-7e63-447d-bb35-0557627f4b41", + "updated": "2019-10-06T23:01:38.282Z", + "status": "completed" + } + ], + "id": "2f984898-de0a-442a-8eed-8d7ce8c9e6a4", + "submissionPhaseId": null, + "updated": "2019-10-06T23:01:34.310Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-10-06T16:48:15.843Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/2ddd0dd5-2b7a-43e3-84a1-44e5414901fc.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "d70b5366-6ec7-4d7c-8fc4-d0fed953a3a6", + "submissionId": "2ddd0dd5-2b7a-43e3-84a1-44e5414901fc", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-06T16:48:20.382Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "57631dcf-e9fd-4683-9886-d863d2e0d4db", + "updated": "2019-10-06T16:48:20.382Z", + "status": "completed" + } + ], + "id": "2ddd0dd5-2b7a-43e3-84a1-44e5414901fc", + "submissionPhaseId": null, + "updated": "2019-10-06T16:48:15.843Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-06T16:23:28.618Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/763b432f-9559-404f-ab06-ca6fec1d345b.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "b4e83b22-b232-4783-96ea-55d34096d31c", + "submissionId": "763b432f-9559-404f-ab06-ca6fec1d345b", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-06T16:23:32.791Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "78a5a192-d394-4ff5-92a7-5d108c6f3b41", + "updated": "2019-10-06T16:23:32.791Z", + "status": "completed" + } + ], + "id": "763b432f-9559-404f-ab06-ca6fec1d345b", + "submissionPhaseId": null, + "updated": "2019-10-06T16:23:28.618Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-10-06T15:45:23.943Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/fed279ab-42da-4b9e-9e0c-b1fb0a001497.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "fef0b1fb-d6a8-4231-857c-fa3bcab9dd89", + "submissionId": "fed279ab-42da-4b9e-9e0c-b1fb0a001497", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-06T15:45:27.285Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "6d34fe56-3667-48a3-8f77-9824f6b64448", + "updated": "2019-10-06T15:45:27.285Z", + "status": "completed" + } + ], + "id": "fed279ab-42da-4b9e-9e0c-b1fb0a001497", + "submissionPhaseId": null, + "updated": "2019-10-06T15:45:23.943Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-10-06T15:44:46.893Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/7a36b3dd-2b6c-4c2a-85ee-00f8da87cbae.zip", + "challengeId": 30095545, + "filename": "8547899.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "948d4913-588b-4f94-a7d5-c5caf7b80dfd", + "submissionId": "7a36b3dd-2b6c-4c2a-85ee-00f8da87cbae", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-06T15:44:51.889Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "84f88241-0aca-4b3b-9bc7-09ca7a657845", + "updated": "2019-10-06T15:44:51.889Z", + "status": "completed" + } + ], + "id": "7a36b3dd-2b6c-4c2a-85ee-00f8da87cbae", + "submissionPhaseId": null, + "updated": "2019-10-06T15:44:46.893Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-10-06T15:08:24.657Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/da0aec4f-894d-46e3-9846-c0e98555005b.zip", + "challengeId": 30095545, + "filename": "test_zip_file.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "b33db1b0-3fb4-4bed-805e-31657c3a6821", + "submissionId": "da0aec4f-894d-46e3-9846-c0e98555005b", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-06T15:08:28.096Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "5a93b21c-af19-460c-8f61-46f03d737652", + "updated": "2019-10-06T15:08:28.096Z", + "status": "completed" + } + ], + "id": "da0aec4f-894d-46e3-9846-c0e98555005b", + "submissionPhaseId": null, + "updated": "2019-10-06T15:08:24.657Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-10-06T15:07:11.287Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/3a12d16c-9cb1-4c7d-bc22-26a5827efe71.zip", + "challengeId": 30095545, + "filename": "test_zip_file.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "a3161429-542a-456d-9098-781ab5972a59", + "submissionId": "3a12d16c-9cb1-4c7d-bc22-26a5827efe71", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-06T15:07:14.125Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "2f1f3b26-d03f-41f9-ba67-fb9f4de18968", + "updated": "2019-10-06T15:07:14.125Z", + "status": "completed" + } + ], + "id": "3a12d16c-9cb1-4c7d-bc22-26a5827efe71", + "submissionPhaseId": null, + "updated": "2019-10-06T15:07:11.287Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "TonyJ", + "created": "2019-10-06T15:07:00.792Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/fd55c8ce-9617-4a7a-9296-b390bd54fa17.zip", + "challengeId": 30095545, + "filename": "test_zip_file.zip", + "createdBy": "TonyJ", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "433d29a1-dff5-4d0f-a2fa-51036c448e8a", + "submissionId": "fd55c8ce-9617-4a7a-9296-b390bd54fa17", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-10-06T15:07:05.252Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "8a3cda40-8206-4afb-bdaf-d6d492d0d0ed", + "updated": "2019-10-06T15:07:05.252Z", + "status": "completed" + } + ], + "id": "fd55c8ce-9617-4a7a-9296-b390bd54fa17", + "submissionPhaseId": null, + "updated": "2019-10-06T15:07:00.792Z", + "fileType": "zip", + "memberId": 8547899 + }, + { + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-09-29T06:46:10.128Z", + "isFileSubmission": true, + "type": "Contest Submission", + "url": "https://s3.amazonaws.com/topcoder-dev-submissions/5804c129-f8c2-4246-a6a3-c662916e3f02.zip", + "challengeId": 30095545, + "legacySubmissionId": 208870, + "filename": "8547899.zip", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "review": [ + { + "score": 100, + "updatedBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "reviewerId": "67a62bcf-6c40-4452-ab20-534016568ec0", + "submissionId": "5804c129-f8c2-4246-a6a3-c662916e3f02", + "createdBy": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC@clients", + "created": "2019-09-29T06:46:30.229Z", + "scoreCardId": 30001850, + "typeId": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3", + "id": "d677877a-abaf-4ec9-8aa7-466a09bd8f6d", + "updated": "2019-09-29T06:46:30.229Z", + "status": "completed" + } + ], + "id": "5804c129-f8c2-4246-a6a3-c662916e3f02", + "submissionPhaseId": null, + "updated": "2019-09-29T06:46:10.128Z", + "fileType": "zip", + "memberId": 8547899 + } +] diff --git a/test/common/testConfig.js b/test/common/testConfig.js new file mode 100644 index 0000000..dad4075 --- /dev/null +++ b/test/common/testConfig.js @@ -0,0 +1,7 @@ +/* + * Configuration for test. + */ + +module.exports = { + WAIT_TIME: process.env.WAIT_TIME || 500 +} diff --git a/test/common/testData.js b/test/common/testData.js index d7447aa..1664366 100755 --- a/test/common/testData.js +++ b/test/common/testData.js @@ -1,22 +1,18 @@ /* * Data for tests. */ -const fs = require('fs-extra') + +const challengeIdWithZeroSubmissions = '20109' +const submissionIdNotInChallenge = 'b8ee3343-f3cf-4f39-9618-04a70b900a68' +const submissionIdWithZeroArtifacts = 'bacacaa1-056f-4438-bf02-c9efe6334b2f' module.exports = { token: { admin: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBTdXBwb3J0IiwiYWRtaW5pc3RyYXRvciIsInRlc3RSb2xlIiwiYWFhIiwidG9ueV90ZXN0XzEiLCJDb25uZWN0IE1hbmFnZXIiLCJDb25uZWN0IEFkbWluIiwiY29waWxvdCIsIkNvbm5lY3QgQ29waWxvdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJUb255SiIsImV4cCI6MTU4MTc5MjIxMSwidXNlcklkIjoiODU0Nzg5OSIsImlhdCI6MTU0OTc5MTYxMSwiZW1haWwiOiJ0amVmdHMrZml4QHRvcGNvZGVyLmNvbSIsImp0aSI6ImY5NGQxZTI2LTNkMGUtNDZjYS04MTE1LTg3NTQ1NDRhMDhmMSJ9.3nxk6c9P1GBWQ__XPsouddjXHAA3s_7t4E83tbFSFCA', + m2m: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5VSkZORGd4UlRVME5EWTBOVVkzTlRkR05qTXlRamxETmpOQk5UYzVRVUV3UlRFeU56TTJRUSJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoibWFFMm1hQlN2OWZSVkhqU2xDMzFMRlpTcTZWaGhacUNAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbTJtLnRvcGNvZGVyLWRldi5jb20vIiwiaWF0IjoxNTc1MjMyNTMyLCJleHAiOjE1NzUzMTg5MzIsImF6cCI6Im1hRTJtYUJTdjlmUlZIalNsQzMxTEZaU3E2VmhoWnFDIiwic2NvcGUiOiJyZWFkOmNoYWxsZW5nZXMgd3JpdGU6Y2hhbGxlbmdlcyByZWFkOmdyb3VwcyB1cGRhdGU6c3VibWlzc2lvbiByZWFkOnN1Ym1pc3Npb24gZGVsZXRlOnN1Ym1pc3Npb24gY3JlYXRlOnN1Ym1pc3Npb24gYWxsOnN1Ym1pc3Npb24gdXBkYXRlOnJldmlld190eXBlIHJlYWQ6cmV2aWV3X3R5cGUgZGVsZXRlOnJldmlld190eXBlIGFsbDpyZXZpZXdfdHlwZSB1cGRhdGU6cmV2aWV3X3N1bW1hdGlvbiByZWFkOnJldmlld19zdW1tYXRpb24gZGVsZXRlOnJldmlld19zdW1tYXRpb24gY3JlYXRlOnJldmlld19zdW1tYXRpb24gYWxsOnJldmlld19zdW1tYXRpb24gdXBkYXRlOnJldmlldyByZWFkOnJldmlldyBkZWxldGU6cmV2aWV3IGNyZWF0ZTpyZXZpZXcgYWxsOnJldmlldyByZWFkOmJ1c190b3BpY3Mgd3JpdGU6YnVzX2FwaSByZWFkOnVzZXJfcHJvZmlsZXMiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.YAunJfjPGZ0UM0an8UI0ISDiE31eWi9LOWUXFT8P_xftn2V0BkOlGpcm6zlMEMS4eR0LAInZS7WY6bfQW7z3Csl7untnrp2EwRh9gQWndcJejf6XizfhEvCwhbAVeS-95sS2vuxsG9WSsAXp6pcrBayzRFPMa5kUzolB1sExeUypkdGI5jR4gDF-NC7B1zHAsseHVyL3SknlDnzSbt0S6rAOX6BEXzaYERgmX5AtIdN4cZ9cwAikQkEj27ZhmYRR4gMaAZLK6sAC9Do7Rbux4yLQwVToAE2S2PQ7ehGHlHveVlCkRx1VGLIBAmsZp9He-t_uWSxU9n7ILDVhMsomTw', idToken: 'fake id token' }, - userId: { - admin: '8547899' - }, - challengeId: { - valid: '30095545' - }, - sampleZipFilename: 'test_zip_file.zip', - sampleZipFile: fs.readFileSync('./test/common/test_zip_file.zip'), responses: { submissionAPI: { OK: { @@ -32,6 +28,12 @@ module.exports = { updatedBy: 'aaron2017', submissionPhaseId: 1128630, fileType: 'zip' + }, + searchSubmissions: require('./submissions.json'), + searchArtifacts: { + artifacts: [ + 'c781b043-17c1-45a5-9004-8515286a1997' + ] } }, membersAPI: { @@ -46,22 +48,24 @@ module.exports = { version: 'v3' } }, - testCodebases: { - withRCFile: './test/common/test_codebases/with_rc_file', - withoutRCFile: './test/common/test_codebases/without_rc_file', - invalidRCFile: - './test/common/test_codebases/rc_file_with_invalid_json_syntax' - }, - sampleRCObject: { - challengeIds: ['30095545'], - userId: '8547899', - username: 'TonyJ', - password: 'appirio123' - }, - sampleIniObject: { + m2mConfig: { m2m: { client_id: 'ajksbsnsj', client_secret: 'vvdhdbhbdhbdn_djdj' } + }, + userCredentials: { + username: 'TonyJ', + password: 'xxx123' + }, + challengeIdWithZeroSubmissions, + submissionIdNotInChallenge, + submissionIdWithZeroArtifacts, + // variables used for the communication between tests and mock server. + _variables: { + submissionDownloadInfo: [], + artifactDownloadInfo: [], + uploadedSubmissionInfo: [], + needErrorResponse: false // used to instruct nock server to return error response or not } } diff --git a/test/common/testHelper.js b/test/common/testHelper.js index fc3cd03..a732e66 100755 --- a/test/common/testHelper.js +++ b/test/common/testHelper.js @@ -2,12 +2,154 @@ * Helper functions for test. */ const AdmZip = require('adm-zip') +const _ = require('lodash') +const fs = require('fs-extra') +const ini = require('ini') +const { ObjectWritableMock } = require('stream-mock') function listZipEntries (buffer) { const zip = new AdmZip(buffer) return zip.getEntries() } +/** + * Build command line arguments. + * + * @param {String} subName the sub-command name + * @param {Object} options the object representing arguments + * @returns {Array} the arguments + */ +function buildArgs (subName, options) { + const args = ['node', 'topcoder-cli.js'] + if (!_.isUndefined(subName)) { + args.push(subName) + } + _.forEach(options, (value, key) => { + if (_.isArray(value)) { + args.push(`--${key}`, ...value) + return + } + if (_.isBoolean(value) && value) { + args.push(`--${key}`) + return + } + args.push(`--${key}`, value) + }) + return args +} + +/** + * Mock a function of a module. + * + * @param {Object} module the module + * @param {String} functionName the function name + * @param {Function} func the mocked function + * @returns {Object} an object contains restore method + */ +function mockFunction (module, functionName, func) { + const originalFunction = module[functionName] + module[functionName] = func + return { + restore: () => { module[functionName] = originalFunction } + } +} + +/** + * Mock RC config so that no actual RC files need to be provided during test. + * + * @param {Object} config the configs + * @returns {Object} an object contains restore method + */ +function mockRCConfig (config) { + const ensureRCFileExists = mockFunction( + fs, 'exists', () => true + ) + const returnRCConfig = mockFunction( + fs, 'readJSON', () => (config) + ) + return { + restore: () => { + ensureRCFileExists.restore() + returnRCConfig.restore() + } + } +} + +/** + * Mock global configuration. + * + * @param {Object} config the configs + * @returns {Object} an object contains restore method + */ +function mockGlobalConfig (config) { + return mockFunction( + fs, 'readFile', () => ini.stringify(config) + ) +} + +/** + * mock functions related download process + * so that no actual downloading happened during the tests. + * + * @param {Array} downloadInfo an array used to store filenames and data + * @returns {Object} an object contains restore method + */ +function mockDownload (downloadInfo) { + let data + const noCreateDirs = mockFunction(fs, 'mkdirp', () => {}) + const mockWriteStream = mockFunction(fs, 'createWriteStream', () => { + const writer = new ObjectWritableMock() + writer.on('finish', () => { + data = writer.data + }) + return writer + }) + const mockMoveFile = mockFunction(fs, 'move', (source, target) => { + downloadInfo.push({ filename: target, data }) + }) + return { + restore: () => { + noCreateDirs.restore() + mockWriteStream.restore() + mockMoveFile.restore() + } + } +} + +/** + * Low-budget multipart parser. + * + * @param {Object} text the raw multipart string + * @returns {Object} parsed object + */ +function parseMultipart (text) { + const textTrimed = text.trim() + const boundary = textTrimed.split('\r\n')[0] + const blocks = _.map( + textTrimed.split(boundary).slice(1, -1), + text => text.trim() + ) + const data = {} + for (const block of blocks) { + const [headerPart, body] = block.split('\r\n\r\n') + const pairs = _.map( + headerPart.match(/(?\S+)="(?\S+)"/g), + text => text.split('=') + ).reduce((object, item) => Object.assign(object, { [item[0]]: item[1].match(/"(?\S+)"/).groups.quoted }), {}) + data[pairs.name] = { + headers: _.omit(pairs, ['name']), + body + } + } + return data +} + module.exports = { - listZipEntries + listZipEntries, + buildArgs, + mockFunction, + mockRCConfig, + mockGlobalConfig, + mockDownload, + parseMultipart } diff --git a/test/common/test_codebases/rc_file_with_invalid_json_syntax/.topcoderrc b/test/common/test_codebases/rc_file_with_invalid_json_syntax/.topcoderrc deleted file mode 100755 index 7f6f1d4..0000000 --- a/test/common/test_codebases/rc_file_with_invalid_json_syntax/.topcoderrc +++ /dev/null @@ -1 +0,0 @@ -This is not a valid topcoderrc file! diff --git a/test/common/test_codebases/rc_file_with_invalid_json_syntax/package.json b/test/common/test_codebases/rc_file_with_invalid_json_syntax/package.json deleted file mode 100755 index db3bdec..0000000 --- a/test/common/test_codebases/rc_file_with_invalid_json_syntax/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "tc-submission-cli", - "version": "1.0.0", - "description": "A CLI tool that will be used by Topcoder members to submit their solutions on challenges.", - "main": "index.js", - "scripts": { - "lint": "standard", - "lint:fix": "standard --fix", - "test": "mocha --require test/prepare.js -t 20000 test/unit.test.js --exit", - "test:cov": "nyc --reporter=html --reporter=text npm test" - }, - "dependencies": { - "adm-zip": "^0.4.11", - "config": "^3.1.0", - "fast-glob": "^2.2.6", - "joi": "^14.0.0", - "lodash": "^4.17.11", - "request": "^2.88.0", - "request-promise-native": "^1.0.7" - }, - "bin": { - "tc-submission-cli": "bin/cli.js" - }, - "devDependencies": { - "chai": "^4.2.0", - "mocha": "^6.1.4", - "mocha-prepare": "^0.1.0", - "nock": "^10.0.6" - } -} diff --git a/test/common/test_codebases/with_rc_file/.topcoderrc b/test/common/test_codebases/with_rc_file/.topcoderrc deleted file mode 100755 index 416b1b8..0000000 --- a/test/common/test_codebases/with_rc_file/.topcoderrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "challengeIds": [ - "30055247", - "30055217" - ], - "username": "TonyJ", - "password": "appirio123" -} diff --git a/test/common/test_codebases/with_rc_file/package.json b/test/common/test_codebases/with_rc_file/package.json deleted file mode 100755 index db3bdec..0000000 --- a/test/common/test_codebases/with_rc_file/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "tc-submission-cli", - "version": "1.0.0", - "description": "A CLI tool that will be used by Topcoder members to submit their solutions on challenges.", - "main": "index.js", - "scripts": { - "lint": "standard", - "lint:fix": "standard --fix", - "test": "mocha --require test/prepare.js -t 20000 test/unit.test.js --exit", - "test:cov": "nyc --reporter=html --reporter=text npm test" - }, - "dependencies": { - "adm-zip": "^0.4.11", - "config": "^3.1.0", - "fast-glob": "^2.2.6", - "joi": "^14.0.0", - "lodash": "^4.17.11", - "request": "^2.88.0", - "request-promise-native": "^1.0.7" - }, - "bin": { - "tc-submission-cli": "bin/cli.js" - }, - "devDependencies": { - "chai": "^4.2.0", - "mocha": "^6.1.4", - "mocha-prepare": "^0.1.0", - "nock": "^10.0.6" - } -} diff --git a/test/common/test_codebases/with_rc_file/src/app.js b/test/common/test_codebases/with_rc_file/src/app.js deleted file mode 100755 index 73add80..0000000 --- a/test/common/test_codebases/with_rc_file/src/app.js +++ /dev/null @@ -1,3 +0,0 @@ -/* - * Dummy module. - */ diff --git a/test/common/test_codebases/without_rc_file/package.json b/test/common/test_codebases/without_rc_file/package.json deleted file mode 100755 index db3bdec..0000000 --- a/test/common/test_codebases/without_rc_file/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "tc-submission-cli", - "version": "1.0.0", - "description": "A CLI tool that will be used by Topcoder members to submit their solutions on challenges.", - "main": "index.js", - "scripts": { - "lint": "standard", - "lint:fix": "standard --fix", - "test": "mocha --require test/prepare.js -t 20000 test/unit.test.js --exit", - "test:cov": "nyc --reporter=html --reporter=text npm test" - }, - "dependencies": { - "adm-zip": "^0.4.11", - "config": "^3.1.0", - "fast-glob": "^2.2.6", - "joi": "^14.0.0", - "lodash": "^4.17.11", - "request": "^2.88.0", - "request-promise-native": "^1.0.7" - }, - "bin": { - "tc-submission-cli": "bin/cli.js" - }, - "devDependencies": { - "chai": "^4.2.0", - "mocha": "^6.1.4", - "mocha-prepare": "^0.1.0", - "nock": "^10.0.6" - } -} diff --git a/test/common/test_zip_file.zip b/test/common/test_zip_file.zip deleted file mode 100755 index 6627fda..0000000 Binary files a/test/common/test_zip_file.zip and /dev/null differ diff --git a/test/config.command.test.js b/test/config.command.test.js new file mode 100644 index 0000000..200120e --- /dev/null +++ b/test/config.command.test.js @@ -0,0 +1,112 @@ +/* + * Test for the Config command. + */ +const chai = require('chai') +const delay = require('delay') +const fs = require('fs-extra') +const _ = require('lodash') +const ini = require('ini') + +const testHelper = require('./common/testHelper') +const testData = require('./common/testData') +const testConfig = require('./common/testConfig') +const logger = require('../src/common/logger') +let { program } = require('../bin/topcoder-cli') + +const validConfig = ['username', 'aaron2017'] +const invalidConfig = ['rank', 1] + +const localTestData = { + globalConfigWithUserCredentials: ini.stringify(testData.userCredentials), + argsWithList: testHelper.buildArgs('config', { list: true }), + argsWithAdd: testHelper.buildArgs('config', { add: validConfig }), + argsWithAddInvalidKey: testHelper.buildArgs('config', { add: invalidConfig }), + argsWithExtraValues: testHelper.buildArgs('config', { add: [...validConfig, 'extra arguments here'] }), + argsWithUnset: testHelper.buildArgs('config', { unset: 'username' }), + argsWithUnsetNotExistentKey: testHelper.buildArgs('config', { unset: invalidConfig[0] }) +} + +describe('Config Command Test', async function () { + const mocks = {} + let content = [] + let errorMessages = [] + + beforeEach(async function () { + mocks.readFile = testHelper.mockFunction(fs, 'readFile', () => localTestData.globalConfigWithUserCredentials) + mocks.writeFile = testHelper.mockFunction(fs, 'writeFile', (path, text) => { content.push(text) }) + const logError = logger.error + mocks.interceptError = testHelper.mockFunction( + logger, 'error', (message) => { + errorMessages.push(message) + logError(message) + } + ) + }) + + afterEach(async function () { + // the commander instance caches arguments after it parses them, which may break tests. + // we have to re-import the topcoder-cli module to clean the cache on every test case. + const newCLI = require('mock-require').reRequire('../bin/topcoder-cli') + program = newCLI.program + // restore functions + for (const mock of Object.values(mocks)) { + mock.restore() + } + // reset values + content = [] + errorMessages = [] + }) + + it('success - list global config', async function () { + const messages = [] + const logInfo = console.log + mocks.interceptInfo = testHelper.mockFunction( + console, 'log', (message) => { + messages.push(message) + logInfo(message) + } + ) + program.parse(localTestData.argsWithList) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(messages)).to.equal(localTestData.globalConfigWithUserCredentials) + }) + + it('success - add global config', async function () { + program.parse(localTestData.argsWithAdd) + await delay(testConfig.WAIT_TIME) + chai.expect(content[0]).to.include('username=aaron2017') + }) + + it('success - add global config when config file not found', async function () { + mocks.readFile.restore() + mocks.readFile = testHelper.mockFunction(fs, 'readFile', () => { throw new Error() }) + program.parse(localTestData.argsWithAdd) + await delay(testConfig.WAIT_TIME) + chai.expect(content[0]).to.include('username=aaron2017') + }) + + it('success - unset global config', async function () { + program.parse(localTestData.argsWithUnset) + await delay(testConfig.WAIT_TIME) + chai.expect(content[0]).to.include('password=xxx123') + chai.expect(content[0]).to.not.include('username=TonyJ') + }) + + it('failure - add global config with invalid key', async function () { + program.parse(localTestData.argsWithAddInvalidKey) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('Invalid key value.') + }) + + it('failure - add global config with extra values', async function () { + program.parse(localTestData.argsWithExtraValues) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('Invalid number of values passed.') + }) + + it('failure - unset global config with non-existent key', async function () { + program.parse(localTestData.argsWithUnsetNotExistentKey) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include(`${invalidConfig[0]} is not found in the config file`) + }) +}) diff --git a/test/fetchArtifacts.command.test.js b/test/fetchArtifacts.command.test.js new file mode 100644 index 0000000..eaaf78c --- /dev/null +++ b/test/fetchArtifacts.command.test.js @@ -0,0 +1,200 @@ +/* + * Test for the FetchArtifacts command. + */ +const chai = require('chai') +const delay = require('delay') +const path = require('path') +const _ = require('lodash') + +const logger = require('../src/common/logger') +const testHelper = require('./common/testHelper') +const testData = require('./common/testData') +const testConfig = require('./common/testConfig') +let { program } = require('../bin/topcoder-cli') + +const submissionId = '4d38573f-404c-4c2f-90b1-a05ccd3fe860' +const legacySubmissionId = '208870' + +const localTestData = { + argsBasic: testHelper.buildArgs('fetch-artifacts'), + argsWithSubmissionId: testHelper.buildArgs('fetch-artifacts', { ...testData.userCredentials, submissionId }), + argsWithoutId: testHelper.buildArgs('fetch-artifacts', { ...testData.userCredentials }), + argsWithoutPassword: testHelper.buildArgs('fetch-artifacts', { ..._.pick(testData.userCredentials, ['username']), submissionId }), + argsWithDev: testHelper.buildArgs('fetch-artifacts', { ...testData.userCredentials, submissionId, dev: true }), + argsWithLegacySubmissionId: testHelper.buildArgs('fetch-artifacts', { ...testData.userCredentials, legacySubmissionId }), + argsWithSubmissionIdContainingZeroArtifacts: testHelper.buildArgs('fetch-artifacts', { ...testData.userCredentials, submissionId: testData.submissionIdWithZeroArtifacts }), + argsWithBothIds: testHelper.buildArgs('fetch-artifacts', { ...testData.userCredentials, submissionId, legacySubmissionId }), + artifactsDownloadDirname: `submission-${submissionId}-artifacts` +} + +describe('FetchArtifacts Command Test', async function () { + const mocks = {} + let messages = [] + let errorMessages = [] + let downloadInfo = [] + + // check if submissions are properly downloaded + const checkDownloadedArtifacts = () => { + const actual = _.map(downloadInfo, info => ({ filename: path.basename(info.filename), data: info.data.toString() })) + const expected = _.map(testData._variables.artifactDownloadInfo, info => ({ filename: info.filename, data: info.data.toString() })) + chai.expect(actual).to.eql(expected) + } + + beforeEach(async function () { + const logInfo = logger.info + mocks.interceptInfo = testHelper.mockFunction( + logger, 'info', (message) => { + messages.push(message) + logInfo(message) + } + ) + const logError = logger.error + mocks.interceptError = testHelper.mockFunction( + logger, 'error', (message) => { + errorMessages.push(message) + logError(message) + } + ) + mocks.returnEmptyRC = testHelper.mockRCConfig({}) + mocks.returnEmptyGlobalConfig = testHelper.mockGlobalConfig({}) + mocks.mockDownload = testHelper.mockDownload(downloadInfo) + }) + + afterEach(async function () { + // the commander instance caches arguments after it parses them, which may break tests. + // we have to re-import the topcoder-cli module to clean the cache on every test case. + const newCLI = require('mock-require').reRequire('../bin/topcoder-cli') + program = newCLI.program + // restore values + messages = [] + errorMessages = [] + downloadInfo = [] + // restore to original functions + for (const mock of Object.values(mocks)) { + mock.restore() + } + // empty the logs for the filenames of downloaded artifacts on each test + testData._variables.artifactDownloadInfo = [] + // restore network state + testData._variables.needErrorResponse = false + // reset NODE_ENV to undefined + _.unset(process.env, 'NODE_ENV') + }) + + it('success - fetch artifacts to local', async function () { + program.parse(localTestData.argsWithSubmissionId) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.artifactDownloadInfo.length).to.equal( + testData.responses.submissionAPI.searchArtifacts.artifacts.length + ) + checkDownloadedArtifacts() + }) + + for (const [credentials, desc] of [ + [testData.m2mConfig, 'm2m'], + [testData.userCredentials, 'user credentials'] + ]) { + it(`success - fetch artifacts to local with ${desc} from rc file`, async function () { + mocks.returnEmptyRC.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ submissionId, ...credentials }) + program.parse(localTestData.argsBasic) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.artifactDownloadInfo.length).to.equal( + testData.responses.submissionAPI.searchArtifacts.artifacts.length + ) + checkDownloadedArtifacts() + }) + + it(`success - fetch artifacts to local with ${desc} from global config`, async function () { + mocks.returnEmptyRC.restore() + mocks.returnEmptyGlobalConfig.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ submissionId }) + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(credentials) + program.parse(localTestData.argsBasic) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.artifactDownloadInfo.length).to.equal( + testData.responses.submissionAPI.searchArtifacts.artifacts.length + ) + checkDownloadedArtifacts() + }) + } + + it('success - fetch artifacts to local with argument dev', async function () { + program.parse(localTestData.argsWithDev) + await delay(testConfig.WAIT_TIME) + chai.expect(process.env.NODE_ENV).to.equal('dev') + chai.expect(testData._variables.artifactDownloadInfo.length).to.equal( + testData.responses.submissionAPI.searchArtifacts.artifacts.length + ) + checkDownloadedArtifacts() + }) + + it('success - fetch artifacts to local with argument legacySubmissionId', async function () { + program.parse(localTestData.argsWithLegacySubmissionId) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.artifactDownloadInfo.length).to.equal( + testData.responses.submissionAPI.searchArtifacts.artifacts.length + ) + checkDownloadedArtifacts() + }) + + it('success - show info if no artifacts found', async function () { + program.parse(localTestData.argsWithSubmissionIdContainingZeroArtifacts) + await delay(testConfig.WAIT_TIME) + chai.expect(_.nth(messages, -2)).to.equal(`No artifact exists for submission with ID: ${testData.submissionIdWithZeroArtifacts}.`) + chai.expect(_.nth(messages, -1)).to.equal('All Done!') + }) + + it('failure - fetch artifacts missing password', async function () { + program.parse(localTestData.argsWithoutPassword) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('"username" missing required peer "password"') + }) + + it('failure - fetch artifacts missing m2m.client_id', async function () { + mocks.returnEmptyRC.restore() + mocks.returnEmptyGlobalConfig.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ submissionId }) + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(_.pick(testData.m2mConfig, ['m2m.client_secret'])) + program.parse(localTestData.argsBasic) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('m2m.client_id" is required') + }) + + it('failure - fetch artifacts missing m2m.client_secret', async function () { + mocks.returnEmptyRC.restore() + mocks.returnEmptyGlobalConfig.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ submissionId }) + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(_.pick(testData.m2mConfig, ['m2m.client_id'])) + program.parse(localTestData.argsBasic) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('m2m.client_secret" is required') + }) + + it('failure - fetch artifacts without submissionId or legacySubmissionId', async function () { + program.parse(localTestData.argsWithoutId) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('"value" must contain at least one of [submissionId, legacySubmissionId]') + }) + + it('failure - fetch artifacts provided both submissionId and legacySubmissionId', async function () { + program.parse(localTestData.argsWithBothIds) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('contains a conflict between exclusive peers [submissionId, legacySubmissionId]') + }) + + it('failure - it should handle possible request errors', async function () { + testData._variables.needErrorResponse = true // instruct nock server to return 500 + program.parse(localTestData.argsWithSubmissionId) + await delay(testConfig.WAIT_TIME) + chai.expect(_.nth(errorMessages, 0)).to.include('Couldn\'t download artifact') + }) + + it('failure - fetch artifacts provided both m2m and userCredentials', async function () { + mocks.returnEmptyRC.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ submissionId, ...testData.m2mConfig }) + program.parse(localTestData.argsWithSubmissionId) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('contains a conflict between exclusive peers [username, m2m]') + }) +}) diff --git a/test/fetchSubmissions.command.test.js b/test/fetchSubmissions.command.test.js new file mode 100644 index 0000000..834390e --- /dev/null +++ b/test/fetchSubmissions.command.test.js @@ -0,0 +1,230 @@ +/* + * Test for the FetchSubmissions command. + */ +const chai = require('chai') +const delay = require('delay') +const path = require('path') +const _ = require('lodash') + +const logger = require('../src/common/logger') +const testHelper = require('./common/testHelper') +const testData = require('./common/testData') +const testConfig = require('./common/testConfig') +let { program } = require('../bin/topcoder-cli') + +const challengeId = '30095545' +const memberId = 40687077 +const latest = true +const submissionId = '4d38573f-404c-4c2f-90b1-a05ccd3fe860' + +const localTestData = { + argsBasic: testHelper.buildArgs('fetch-submissions'), + argsWithChallengeId: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials, challengeId }), + argsWithoutChallengeId: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials }), + argsWithoutPassword: testHelper.buildArgs('fetch-submissions', { ..._.pick(testData.userCredentials, ['username']), challengeId }), + argsWithDev: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials, challengeId, dev: true }), + argsWithLatest: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials, challengeId, latest }), + argsWithMemberId: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials, challengeId, memberId }), + argsWithSubmissionId: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials, challengeId, submissionId }), + argsWithSubmissionIdNotInChallenge: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials, challengeId, submissionId: testData.submissionIdNotInChallenge }), + argsWithChallengeIdContainingZeroSubmissions: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials, challengeId: testData.challengeIdWithZeroSubmissions }), + argsWithBothSubmissionIdAndMemberId: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials, challengeId, submissionId, memberId }), + argsWithBothSubmissionIdAndLatest: testHelper.buildArgs('fetch-submissions', { ...testData.userCredentials, challengeId, submissionId, latest }), + submissionsDownloadDirname: `${challengeId}-submissions` +} + +describe('FetchSubmissions Command Test', async function () { + const mocks = {} + let messages = [] + let errorMessages = [] + let downloadInfo = [] + + // check if submissions are properly downloaded + const checkDownloadedSubmissions = () => { + const actual = _.map(downloadInfo, info => ({ filename: path.basename(info.filename), data: info.data.toString() })) + const expected = _.map(testData._variables.submissionDownloadInfo, info => ({ filename: info.filename, data: info.data.toString() })) + chai.expect(actual).to.eql(expected) + } + + beforeEach(async function () { + const logInfo = logger.info + mocks.interceptInfo = testHelper.mockFunction( + logger, 'info', (message) => { + messages.push(message) + logInfo(message) + } + ) + const logError = logger.error + mocks.interceptError = testHelper.mockFunction( + logger, 'error', (message) => { + errorMessages.push(message) + logError(message) + } + ) + mocks.returnEmptyRC = testHelper.mockRCConfig({}) + mocks.returnEmptyGlobalConfig = testHelper.mockGlobalConfig({}) + mocks.mockDownload = testHelper.mockDownload(downloadInfo) + }) + + afterEach(async function () { + // the commander instance caches arguments after it parses them, which may break tests. + // we have to re-import the topcoder-cli module to clean the cache on every test case. + const newCLI = require('mock-require').reRequire('../bin/topcoder-cli') + program = newCLI.program + // restore values + messages = [] + errorMessages = [] + downloadInfo = [] + // restore to original functions + for (const mock of Object.values(mocks)) { + mock.restore() + } + // reset NODE_ENV to undefined + _.unset(process.env, 'NODE_ENV') + // empty the logs for the downloaded submissions on each test + testData._variables.submissionDownloadInfo = [] + // restore network state + testData._variables.needErrorResponse = false + }) + + it('success - fetch submissions to local', async function () { + program.parse(localTestData.argsWithChallengeId) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.submissionDownloadInfo.length).to.equal( + testData.responses.submissionAPI.searchSubmissions.length + ) + checkDownloadedSubmissions() + }) + + for (const [credentials, desc] of [ + [testData.m2mConfig, 'm2m'], + [testData.userCredentials, 'user credentials'] + ]) { + it(`success - fetch submissions to local with ${desc} from rc file`, async function () { + mocks.returnEmptyRC.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ challengeId, ...credentials }) + program.parse(localTestData.argsBasic) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.submissionDownloadInfo.length).to.equal( + testData.responses.submissionAPI.searchSubmissions.length + ) + checkDownloadedSubmissions() + }) + + it(`success - fetch submissions to local with ${desc} from global config`, async function () { + mocks.returnEmptyRC.restore() + mocks.returnEmptyGlobalConfig.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ challengeId }) + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(credentials) + program.parse(localTestData.argsBasic) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.submissionDownloadInfo.length).to.equal( + testData.responses.submissionAPI.searchSubmissions.length + ) + checkDownloadedSubmissions() + }) + } + + it('success - fetch submissions to local with argument dev', async function () { + program.parse(localTestData.argsWithDev) + await delay(testConfig.WAIT_TIME) + chai.expect(process.env.NODE_ENV).to.equal('dev') + chai.expect(testData._variables.submissionDownloadInfo.length).to.equal( + testData.responses.submissionAPI.searchSubmissions.length + ) + checkDownloadedSubmissions() + }) + + it('success - fetch submissions to local with argument latest', async function () { + program.parse(localTestData.argsWithLatest) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.submissionDownloadInfo.length).to.equal(2) + checkDownloadedSubmissions() + }) + + it('success - fetch submissions to local with argument memberId', async function () { + program.parse(localTestData.argsWithMemberId) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.submissionDownloadInfo.length).to.equal(1) + checkDownloadedSubmissions() + }) + + it('success - fetch submissions to local with argument submissionId', async function () { + program.parse(localTestData.argsWithSubmissionId) + await delay(testConfig.WAIT_TIME) + chai.expect(testData._variables.submissionDownloadInfo.length).to.equal(1) + checkDownloadedSubmissions() + }) + + it('success - show info if no submissions found', async function () { + program.parse(localTestData.argsWithChallengeIdContainingZeroSubmissions) + await delay(testConfig.WAIT_TIME) + chai.expect(_.nth(messages, -2)).to.equal(`No submissions exists with specified filters for challenge with ID: ${testData.challengeIdWithZeroSubmissions}.`) + chai.expect(_.nth(messages, -1)).to.equal('All Done!') + }) + + it('failure - fetch submissions without challengeId', async function () { + program.parse(localTestData.argsWithoutChallengeId) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('"challengeId" is required') + }) + + it('failure - fetch submissions with submissionId not belong to a challenge', async function () { + program.parse(localTestData.argsWithSubmissionIdNotInChallenge) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('Submission doesn\'t belong to specified challenge.') + }) + + it('failure - it should handle possible request errors', async function () { + testData._variables.needErrorResponse = true // instruct nock server to return 500 + program.parse(localTestData.argsWithSubmissionId) + await delay(testConfig.WAIT_TIME) + chai.expect(_.nth(errorMessages, -2)).to.include(`Couldn't download submission with id: ${submissionId}`) + }) + + it('failure - fetch submissions missing password', async function () { + program.parse(localTestData.argsWithoutPassword) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('"username" missing required peer "password"') + }) + + it('failure - fetch submissions missing m2m.client_id', async function () { + mocks.returnEmptyRC.restore() + mocks.returnEmptyGlobalConfig.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ challengeId }) + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(_.pick(testData.m2mConfig, ['m2m.client_secret'])) + program.parse(localTestData.argsBasic) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('m2m.client_id" is required') + }) + + it('failure - fetch submissions missing m2m.client_secret', async function () { + mocks.returnEmptyRC.restore() + mocks.returnEmptyGlobalConfig.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ challengeId }) + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(_.pick(testData.m2mConfig, ['m2m.client_id'])) + program.parse(localTestData.argsBasic) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('m2m.client_secret" is required') + }) + + it('failure - fetch submissions provided both m2m and userCredentials', async function () { + mocks.returnEmptyRC.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ challengeId, ...testData.m2mConfig }) + program.parse(localTestData.argsWithChallengeId) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('contains a conflict between exclusive peers [username, m2m]') + }) + + it('failure - fetch submissions provided both submissionId and memberId', async function () { + program.parse(localTestData.argsWithBothSubmissionIdAndMemberId) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('Validation failed: "submissionId" conflict with forbidden peer "memberId"') + }) + + it('failure - fetch submissions provided both submissionId and latest', async function () { + program.parse(localTestData.argsWithBothSubmissionIdAndLatest) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('Validation failed: "submissionId" conflict with forbidden peer "latest"') + }) +}) diff --git a/test/pay.command.test.js b/test/pay.command.test.js new file mode 100644 index 0000000..4c5d7a0 --- /dev/null +++ b/test/pay.command.test.js @@ -0,0 +1,74 @@ +/* + * Test for the Pay command. + */ +const chai = require('chai') +const prompts = require('prompts') +const delay = require('delay') +const _ = require('lodash') + +const logger = require('../src/common/logger') +const testHelper = require('./common/testHelper') +const testConfig = require('./common/testConfig') +const { program } = require('../bin/topcoder-cli') + +const localTestData = { + injectedResponse: { + title: 'Topcoder CLI Test Update', + description: 'Create robust test cases for topcoder-cli', + payeeUsername: 'aaron2017', + nda: false, + copilotPayment: 200 + }, + argsWithoutOption: testHelper.buildArgs('pay'), + argsWithDev: testHelper.buildArgs('pay', { dev: true }), + argsWithOptionCopilot: testHelper.buildArgs('pay', { copilot: 'denis' }) +} + +describe('Pay Command Test', async function () { + const mocks = {} + let messages = [] + + before(async function () { + const logInfo = logger.info + mocks.interceptInfo = testHelper.mockFunction( + logger, 'info', (message) => { + messages.push(message) + logInfo(message) + } + ) + }) + + after(async function () { + for (const mock of Object.values(mocks)) { + mock.restore() + } + }) + + afterEach(async function () { + messages = [] + // reset NODE_ENV to undefined + _.unset(process.env, 'NODE_ENV') + }) + + it('prompts - it should capture user response', async function () { + prompts.inject(Object.values(localTestData.injectedResponse)) + program.parse(localTestData.argsWithoutOption) + await delay(testConfig.WAIT_TIME) + chai.expect(messages[0]).to.eql(localTestData.injectedResponse) + }) + + it('prompts - it should capture user response with argument dev', async function () { + prompts.inject(Object.values(localTestData.injectedResponse)) + program.parse(localTestData.argsWithDev) + await delay(testConfig.WAIT_TIME) + chai.expect(process.env.NODE_ENV).to.equal('dev') + chai.expect(messages[0]).to.eql(localTestData.injectedResponse) + }) + + it('prompts - it should not ask for copilotPayment if the copilot argument is provided', async function () { + prompts.inject(Object.values(localTestData.injectedResponse)) + program.parse(localTestData.argsWithOptionCopilot) + await delay(testConfig.WAIT_TIME) + chai.expect(messages[0]).to.eql(_.omit(localTestData.injectedResponse, 'copilotPayment')) + }) +}) diff --git a/test/prepare.js b/test/prepare.js index 5119c0e..fb66ec1 100755 --- a/test/prepare.js +++ b/test/prepare.js @@ -3,10 +3,13 @@ */ const prepare = require('mocha-prepare') const nock = require('nock') -const config = require('../src/config')() const _ = require('lodash') -const testData = require('./common/testData') const { URL } = require('url') +const uuid = require('uuid') + +const config = require('../src/config')() +const testHelper = require('./common/testHelper') +const testData = require('./common/testData') const submissionAPIURL = new URL(config.SUBMISSION_API_URL + '/submissions') const authnAPIURL = new URL(config.TC_AUTHN_URL) @@ -14,12 +17,38 @@ const authzAPIURL = new URL(config.TC_AUTHZ_URL) const membersAPIURL = new URL(config.TC_MEMBERS_API) const m2mURL = new URL(config.AUTH0_URL) +const uuidExpr = '\\b[0-9a-f]{8}\\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\\b[0-9a-f]{12}\\b' + prepare( function (done) { nock(/.com/) .persist() .filteringPath(path => { - if (path === submissionAPIURL.pathname) { + if (path.includes(submissionAPIURL.pathname)) { + if (path.includes('/artifacts')) { + if (path.includes('/download')) { + if (testData._variables.needErrorResponse) { + return 'downloadArtifactError' + } + return 'downloadArtifact' + } + return 'artifacts' + } + if (path.includes('legacySubmissionId=')) { + return 'submissionsWithLegacySubmissionId' + } + if (path.includes('memberId=')) { + return 'submissionsForMember' + } + if (path.includes('/download')) { + if (testData._variables.needErrorResponse) { + return 'downloadSubmissionError' + } + return 'downloadSubmission' + } + if (path.match(new RegExp(`submissions/${uuidExpr}$`))) { + return 'getSubmissionById' + } return 'submissions' } if (path === authnAPIURL.pathname) { @@ -36,9 +65,72 @@ prepare( } return path }) + .get('downloadSubmissionError') + .reply(500) + .get('downloadSubmission') + .reply(200, + (uri) => { + const data = Buffer.from(`This file is the response for request: ${uri}`) + testData._variables.submissionDownloadInfo.push({ data }) + return data + }, { + 'Content-Disposition': () => { + const filename = `test_submission_${uuid()}.txt` // generate unique filename + _.last(testData._variables.submissionDownloadInfo).filename = filename + return `attachment; filename=${filename}` + } + }) + .get('submissions') + .reply(200, (uri) => { + if (uri.includes(testData.challengeIdWithZeroSubmissions)) { + return [] + } + return testData.responses.submissionAPI.searchSubmissions + }) + .get('submissionsWithLegacySubmissionId') + .reply(200, [testData.responses.submissionAPI.searchSubmissions[1]]) + .get('submissionsForMember') + .reply(200, [testData.responses.submissionAPI.searchSubmissions[0]]) + .get('getSubmissionById') + .reply((uri) => { + if (uri.includes(testData.submissionIdNotInChallenge)) { + return [200, testData.responses.submissionAPI.searchSubmissions[0]] + } + return [200, testData.responses.submissionAPI.searchSubmissions[1]] + }) .post('submissions') - .reply(200, testData.responses.submissionAPI.OK) - .post('authV2', _.matches({ username: testData.sampleRCObject.username })) + .reply((uri, requestBody) => { + if (testData._variables.needErrorResponse) { + return [500, ''] + } + const multipartData = testHelper.parseMultipart(requestBody) + testData._variables.uploadedSubmissionInfo.push(multipartData) + + return [200, testData.responses.submissionAPI.OK] + }) + .get('artifacts') + .reply(200, (uri) => { + if (uri.includes(testData.submissionIdWithZeroArtifacts)) { + return [] + } + return testData.responses.submissionAPI.searchArtifacts + }) + .get('downloadArtifactError') + .reply(500) + .get('downloadArtifact') + .reply(200, + (uri) => { + const data = Buffer.from(`This file is the response for request: ${uri}`) + testData._variables.artifactDownloadInfo.push({ data }) + return data + }, { + 'Content-Disposition': () => { + const filename = `test_artifact_${uuid()}.txt` // generate unique filename + _.last(testData._variables.artifactDownloadInfo).filename = filename + return `attachment; filename=${filename}` + } + }) + .post('authV2', _.matches({ username: testData.userCredentials.username })) .reply(200, { id_token: testData.token.idToken }) @@ -59,7 +151,7 @@ prepare( .reply(200, testData.responses.membersAPI) .post('m2mAuth') .reply(200, { - access_token: 'smellycat', + access_token: testData.token.m2m, expiry: 8400 }) done() diff --git a/test/submit.command.test.js b/test/submit.command.test.js new file mode 100644 index 0000000..fb84fdb --- /dev/null +++ b/test/submit.command.test.js @@ -0,0 +1,195 @@ +/* + * Test for the Submit command. + */ +const chai = require('chai') +const delay = require('delay') +const _ = require('lodash') +const mock = require('mock-require') + +const logger = require('../src/common/logger') +const testHelper = require('./common/testHelper') +const testData = require('./common/testData') +const testConfig = require('./common/testConfig') +let { program } = require('../bin/topcoder-cli') + +const challengeIds = '30095545' +const challengeIdNotExist = '123456789' +const memberId = '8547899' +const contentInSubmission = 'empty content' + +const uploadedSubmissionInfo = { + memberId: { headers: {}, body: memberId }, + challengeId: { headers: {}, body: challengeIds }, + type: { headers: {}, body: 'Contest Submission' }, + submission: { headers: { filename: `${memberId}.zip` }, body: contentInSubmission } +} + +const localTestConfig = { + WAIT_TIME: testConfig.WAIT_TIME +} +const localTestData = { + argsBasic: testHelper.buildArgs('submit'), + argsWithChallengeIds: testHelper.buildArgs('submit', { ...testData.userCredentials, challengeIds }), + argsWithoutChallengeId: testHelper.buildArgs('submit', { ...testData.userCredentials }), + argsWithoutPassword: testHelper.buildArgs('submit', { ..._.pick(testData.userCredentials, 'username'), challengeIds }), + argsWithDev: testHelper.buildArgs('submit', { ...testData.userCredentials, challengeIds, dev: true }), + argsWithChallengeIdsNotExist: testHelper.buildArgs('submit', { ...testData.userCredentials, challengeIds: challengeIdNotExist }), + argsWithOnlyChallengeIds: testHelper.buildArgs('submit', { challengeIds }), + challengeIdNotExist, + memberId, + contentInSubmission, + uploadedSubmissionInfo +} + +describe('Submit Command Test', async function () { + const mocks = {} + let messages = [] + let errorMessages = [] + + before(async function () { + // mock the adm-zip module used in UploadSubmission Service + // so that we don't acutally create zip file from local file system. + mock('adm-zip', class { + addLocalFile () {} + + toBuffer () { + return Buffer.from(localTestData.contentInSubmission) + } + }) + mock.reRequire('../src/services/uploadSubmissionService') + mock.reRequire('../src/commands/submit') + program = mock.reRequire('../bin/topcoder-cli').program + }) + + beforeEach(async function () { + const logInfo = logger.info + mocks.interceptInfo = testHelper.mockFunction( + logger, 'info', (message) => { + messages.push(message) + logInfo(message) + } + ) + const logError = logger.error + mocks.interceptError = testHelper.mockFunction( + logger, 'error', (message) => { + errorMessages.push(message) + logError(message) + } + ) + mocks.returnEmptyRC = testHelper.mockRCConfig({}) + mocks.returnEmptyGlobalConfig = testHelper.mockGlobalConfig({}) + }) + + afterEach(async function () { + // the commander instance caches arguments after it parses them, which may break tests. + // we have to re-import the topcoder-cli module to clean the cache on every test case. + program = mock.reRequire('../bin/topcoder-cli').program + // reset values + messages = [] + errorMessages = [] + // restore to original functions + for (const mock of Object.values(mocks)) { + mock.restore() + } + _.unset(process.env, 'NODE_ENV') + // restore network state on each test + testData._variables.needErrorResponse = false + // restore on each test + testData._variables.uploadedSubmissionInfo = [] + }) + + it('success - upload a submission', async function () { + program.parse(localTestData.argsWithChallengeIds) + await delay(localTestConfig.WAIT_TIME) + chai.expect(_.nth(messages, -2)).to.include('[1/1] Uploaded Submission:') + chai.expect(_.last(messages)).to.include('All Done!') + chai.expect(testData._variables.uploadedSubmissionInfo[0]).to.eql(localTestData.uploadedSubmissionInfo) + }) + + for (const [credentials, desc] of [ + [testData.m2mConfig, 'm2m'], + [testData.userCredentials, 'user credentials'] + ]) { + it(`success - upload a submission with ${desc} from rc file`, async function () { + mocks.returnEmptyRC.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ challengeIds: [challengeIds], memberId: localTestData.memberId, ...credentials }) + program.parse(localTestData.argsBasic) + await delay(localTestConfig.WAIT_TIME) + chai.expect(_.nth(messages, -2)).to.include('[1/1] Uploaded Submission:') + chai.expect(_.last(messages)).to.include('All Done!') + chai.expect(testData._variables.uploadedSubmissionInfo[0]).to.eql(localTestData.uploadedSubmissionInfo) + }) + + it(`success - upload a submission with ${desc} from global config`, async function () { + mocks.returnEmptyRC.restore() + mocks.returnEmptyGlobalConfig.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ challengeIds: [challengeIds], memberId: localTestData.memberId }) + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(credentials) + program.parse(localTestData.argsBasic) + await delay(localTestConfig.WAIT_TIME) + chai.expect(_.nth(messages, -2)).to.include('[1/1] Uploaded Submission:') + chai.expect(_.last(messages)).to.include('All Done!') + chai.expect(testData._variables.uploadedSubmissionInfo[0]).to.eql(localTestData.uploadedSubmissionInfo) + }) + } + + it('success - upload a submission with argument dev', async function () { + program.parse(localTestData.argsWithDev) + await delay(localTestConfig.WAIT_TIME) + chai.expect(process.env.NODE_ENV).to.equal('dev') + chai.expect(_.nth(messages, -2)).to.include('[1/1] Uploaded Submission:') + chai.expect(_.last(messages)).to.include('All Done!') + chai.expect(testData._variables.uploadedSubmissionInfo[0]).to.eql(localTestData.uploadedSubmissionInfo) + }) + + it('failure - it should handle possible request errors', async function () { + testData._variables.needErrorResponse = true // instruct nock server to return 500 + program.parse(localTestData.argsWithChallengeIdsNotExist) + await delay(localTestConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include(`Error while uploading submission to challenge ID ${localTestData.challengeIdNotExist}`) + }) + + it('failure - upload a submission without challengeId', async function () { + program.parse(localTestData.argsWithoutChallengeId) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('"challengeIds" is required') + }) + + it('failure - upload a submission missing password', async function () { + program.parse(localTestData.argsWithoutPassword) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('"username" missing required peer "password"') + }) + + it('failure - upload a submission missing m2m.client_id', async function () { + mocks.returnEmptyGlobalConfig.restore() + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(_.pick(testData.m2mConfig, ['m2m.client_secret'])) + program.parse(localTestData.argsWithOnlyChallengeIds) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('m2m.client_id" is required') + }) + + it('failure - upload a submission missing m2m.client_secret', async function () { + mocks.returnEmptyGlobalConfig.restore() + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(_.pick(testData.m2mConfig, ['m2m.client_id'])) + program.parse(localTestData.argsWithOnlyChallengeIds) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('m2m.client_secret" is required') + }) + + it('failure - upload a submission provided m2m config without memberId', async function () { + mocks.returnEmptyGlobalConfig.restore() + mocks.mockGlobalConfig = testHelper.mockGlobalConfig(testData.m2mConfig) + program.parse(localTestData.argsWithOnlyChallengeIds) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('Validation failed: "m2m" missing required peer "memberId"') + }) + + it('failure - upload a submission provided both m2m and userCredentials', async function () { + mocks.returnEmptyRC.restore() + mocks.mockRCConfig = testHelper.mockRCConfig({ challengeIds: [challengeIds], memberId: localTestData.memberId, ...testData.m2mConfig }) + program.parse(localTestData.argsWithChallengeIds) + await delay(testConfig.WAIT_TIME) + chai.expect(_.last(errorMessages)).to.include('contains a conflict between exclusive peers [username, m2m]') + }) +}) diff --git a/test/unit.test.js b/test/unit.test.js deleted file mode 100755 index 00e0fb3..0000000 --- a/test/unit.test.js +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Topcoder CLI Unit tests - */ - -describe('Topcoder CLI Unit tests', async function () {})