diff --git a/README.md b/README.md index e2dcdfcd..75723530 100755 --- a/README.md +++ b/README.md @@ -171,6 +171,24 @@ To migrate the existing data from DynamoDB to ES, run the following script npm run db-to-es ``` +#### Store v5 challenge id for current records + +Submission API started off using the legacy challenge ids. With the v5 upgrade to the challenge api, we now need to make use of the v5 challenge ids. We have thus created a script to update existing `challengeId` attribute on submissions to v5 and store the older challenge ids in the `legacyChallengeId` attribute. + +To update the existing challengeId data on submissions in DynamoDB to v5 challengeId, set the following env variables: + +```bash +SUBMISSION_TABLE_NAME // Table name of the submission records. Defaults to 'Submission' +UPDATE_V5_CHALLENGE_BATCH_SIZE // Number of records that are updated simultaneously. Defaults to 250 +``` + + +and then run the following script + +``` +npm run update-to-v5-challengeId +``` + #### Swagger UI Swagger UI will be served at `http://localhost:3000/docs` diff --git a/config/default.js b/config/default.js index aaf4412b..cfef076f 100755 --- a/config/default.js +++ b/config/default.js @@ -21,8 +21,8 @@ module.exports = { BUSAPI_URL: process.env.BUSAPI_URL || 'https://api.topcoder-dev.com/v5', KAFKA_ERROR_TOPIC: process.env.KAFKA_ERROR_TOPIC || 'error.notification', KAFKA_AGGREGATE_TOPIC: process.env.KAFKA_AGGREGATE_TOPIC || 'submission.notification.aggregate', - CHALLENGEAPI_URL: process.env.CHALLENGEAPI_URL || 'https://api.topcoder-dev.com/v4/challenges', CHALLENGEAPI_V5_URL: process.env.CHALLENGEAPI_V5_URL || 'https://api.topcoder-dev.com/v5/challenges', + RESOURCEAPI_V5_BASE_URL: process.env.RESOURCEAPI_V5_BASE_URL || 'https://api.topcoder-dev.com/v5', AUTH0_URL: process.env.AUTH0_URL, // Auth0 credentials for Submission Service AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE || 'https://www.topcoder.com', TOKEN_CACHE_TIME: process.env.TOKEN_CACHE_TIME, @@ -37,5 +37,7 @@ module.exports = { PAGE_SIZE: process.env.PAGE_SIZE || 20, MAX_PAGE_SIZE: parseInt(process.env.MAX_PAGE_SIZE) || 100, ES_BATCH_SIZE: process.env.ES_BATCH_SIZE || 250, + UPDATE_V5_CHALLENGE_BATCH_SIZE: process.env.UPDATE_V5_CHALLENGE_BATCH_SIZE || 250, + SUBMISSION_TABLE_NAME: process.env.SUBMISSION_TABLE_NAME || 'Submission', AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL } diff --git a/config/test.js b/config/test.js index c702b1f7..4b9a3680 100644 --- a/config/test.js +++ b/config/test.js @@ -7,7 +7,7 @@ module.exports = { LOG_LEVEL: 'info', WEB_SERVER_PORT: 3010, AUTH_SECRET: 'mysecret', - VALID_ISSUERS: '["https://api.topcoder.com"]', + VALID_ISSUERS: process.env.VALID_ISSUERS ? process.env.VALID_ISSUERS.replace(/\\"/g, '') : '["https://api.topcoder.com","https://topcoder-dev.auth0.com/"]', API_VERSION: process.env.API_VERSION || '/api/v5', aws: { AWS_REGION: process.env.AWS_REGION || 'us-east-1', // AWS Region to be used by the application @@ -16,14 +16,14 @@ module.exports = { S3_BUCKET: process.env.S3_BUCKET_TEST || 'tc-testing-submissions' // S3 Bucket to which submissions need to be uploaded }, BUSAPI_EVENTS_URL: 'https://api.topcoder-dev.com/v5/bus/events', - CHALLENGEAPI_URL: 'https://api.topcoder-dev.com/v4/challenges', + BUSAPI_URL: 'https://api.topcoder-dev.com/v5', + CHALLENGEAPI_V5_URL: 'https://api.topcoder-dev.com/v5/challenges', esConfig: { ES_INDEX: process.env.ES_INDEX_TEST || 'submission-test', ES_TYPE: process.env.ES_TYPE_TEST || '_doc' // ES 6.x accepts only 1 Type per index and it's mandatory to define it }, AUTH0_URL: process.env.AUTH0_URL, // Auth0 credentials for Submission Service - AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE || 'https://www.topcoder.com', - TOKEN_CACHE_TIME: process.env.TOKEN_CACHE_TIME, + AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE, AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET, USER_TOKEN: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6IlNoYXJhdGhrdW1hcjkyIiwiZXhwIjo1NTUzMDE5OTI1OSwidXNlcklkIjoiNDA0OTMwNTAiLCJpYXQiOjE1MzAxOTg2NTksImVtYWlsIjoiU2hhcmF0aGt1bWFyOTJAdG9wY29kZXIuY29tIiwianRpIjoiYzNhYzYwOGEtNTZiZS00NWQwLThmNmEtMzFmZTk0Yjk1NjFjIn0.2gtNJwhcv7MYc-muX3Nv-B0RdWbhMRl7-xrwFUsLazM', diff --git a/package.json b/package.json index b09e8038..14ec3b52 100755 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "delete-index": "node scripts/deleteIndex.js", "init-es": "node scripts/loadES.js", "db-to-es": "node scripts/migrateFromDBToES.js", + "update-to-v5-challengeId": "node scripts/updateToV5ChallengeId.js", "test": "mocha test/unit/*.test.js --require test/unit/prepare.js --exit", "e2e": "mocha test/e2e/*.test.js --require test/e2e/prepare.js --exit", "cov": "nyc --reporter=html --reporter=text mocha test/unit/*.test.js --require test/unit/prepare.js --exit", diff --git a/scripts/createIndex.js b/scripts/createIndex.js index 0780a041..eb39d293 100644 --- a/scripts/createIndex.js +++ b/scripts/createIndex.js @@ -18,6 +18,7 @@ co(function * createIndex () { properties: { resource: { type: 'keyword' }, challengeId: { type: 'keyword' }, + legacyChallengeId: { type: 'keyword' }, memberId: { type: 'keyword' }, type: { type: 'keyword' }, isFileSubmission: { type: 'boolean' }, diff --git a/scripts/updateToV5ChallengeId.js b/scripts/updateToV5ChallengeId.js new file mode 100644 index 00000000..b12ace12 --- /dev/null +++ b/scripts/updateToV5ChallengeId.js @@ -0,0 +1,78 @@ +/** + * Store v5 challenge id for current records + */ + +const _ = require('lodash') +const co = require('co') +const config = require('config') +const logger = require('../src/common/logger') +const dbhelper = require('../src/common/dbhelper') +const helper = require('../src/common/helper') + +/** + * Update Submission's challenge id to v5 + * @param {Object} submission The submission record + * @returns {Promise} + */ +function * updateRecord (submission) { + const v5challengeId = yield helper.getV5ChallengeId(submission.challengeId) + const record = { + TableName: 'Submission', + Key: { + id: submission.id + }, + UpdateExpression: `set challengeId = :c, legacyChallengeId = :l`, + ExpressionAttributeValues: { + ':c': v5challengeId, + ':l': submission.challengeId + } + } + if (!v5challengeId) { + logger.warn(`the challengeId: ${submission.challengeId} is not having a v5 challengeId`) + + return + } else if (v5challengeId === submission.challengeId) { + logger.info(`the challengeId: ${submission.challengeId} is already a v5 challengeId`) + } + + yield dbhelper.updateRecord(record) +} + +/* + * Update all submission's challenge id to v5 + * @returns {Promise} + */ +function * updateRecords () { + const tableName = config.SUBMISSION_TABLE_NAME + let promises = [] + const params = { + TableName: tableName + } + // Process until all the records from DB is fetched + while (true) { + const records = yield dbhelper.scanRecords(params) + const totalRecords = records.Items.length + logger.debug(`Number of ${tableName}s fetched from DB - ${totalRecords}. More fetch iterations may follow (pagination in progress)`) + for (let i = 0; i < totalRecords; i++) { + const record = records.Items[i] + promises.push(updateRecord(record)) + } + // Continue fetching the remaining records from Database + if (typeof records.LastEvaluatedKey !== 'undefined') { + params.ExclusiveStartKey = records.LastEvaluatedKey + } else { + break // If there are no more records to process, exit the loop + } + } + logger.debug(`All records fetched. Proceeding to update them in batches of ${config.UPDATE_V5_CHALLENGE_BATCH_SIZE}`) + const paraRecords = _.chunk(promises, config.UPDATE_V5_CHALLENGE_BATCH_SIZE) + for (const rs of paraRecords) { + yield rs + } +} + +co(function * () { + yield updateRecords() +}).catch((err) => { + logger.logFullError(err) +}) diff --git a/src/common/helper.js b/src/common/helper.js index 0cd32dae..bce61c86 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -53,7 +53,7 @@ function autoWrapExpress (obj) { return obj } _.each(obj, (value, key) => { - obj[key] = autoWrapExpress(value); //eslint-disable-line + obj[key] = autoWrapExpress(value); //eslint-disable-line }) return obj } @@ -323,6 +323,30 @@ function * getLegacyChallengeId (challengeId) { return challengeId } +/** + * Get v5 challenge id (uuid) if legacy challenge id + * @param {Integer} challengeId Challenge ID + * @returns {String} v5 uuid Challenge ID of the given challengeId + */ +function * getV5ChallengeId (challengeId) { + if (!(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(challengeId))) { + logger.debug(`${challengeId} detected as legacy challenge id. Fetching legacy challenge id`) + const token = yield getM2Mtoken() + try { + const response = yield request.get(`${config.CHALLENGEAPI_V5_URL}?legacyId=${challengeId}`) + .set('Authorization', `Bearer ${token}`) + .set('Content-Type', 'application/json') + const v5Uuid = _.get(response, 'body[0].id') + logger.debug(`V5 challenge id is ${v5Uuid} for legacy challenge id ${challengeId}`) + return v5Uuid + } catch (err) { + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}?legacyId=${challengeId}`) + throw err + } + } + return challengeId +} + /* * Get submission phase ID of a challenge from Challenge API * @param challengeId Challenge ID @@ -331,24 +355,25 @@ function * getLegacyChallengeId (challengeId) { function * getSubmissionPhaseId (challengeId) { let phaseId = null let response + challengeId = yield getV5ChallengeId(challengeId) try { logger.info(`Calling to challenge API to find submission phase Id for ${challengeId}`) const token = yield getM2Mtoken() - response = yield request.get(`${config.CHALLENGEAPI_URL}/${challengeId}/phases`) + response = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') logger.info(`returned from finding submission phase Id for ${challengeId}`) } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}/${challengeId}/phases`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.debug('Setting submissionPhaseId to Null') response = null } if (response) { - const phases = _.get(response.body, 'result.content', []) - const checkPoint = _.filter(phases, { phaseType: 'Checkpoint Submission', phaseStatus: 'Open' }) - const submissionPh = _.filter(phases, { phaseType: 'Submission', phaseStatus: 'Open' }) - const finalFixPh = _.filter(phases, { phaseType: 'Final Fix', phaseStatus: 'Open' }) + const phases = _.get(response.body, 'phases', []) + const checkPoint = _.filter(phases, { name: 'Checkpoint Submission', isOpen: true }) + const submissionPh = _.filter(phases, { name: 'Submission', isOpen: true }) + const finalFixPh = _.filter(phases, { name: 'Final Fix', isOpen: true }) if (checkPoint.length !== 0) { phaseId = checkPoint[0].id } else if (submissionPh.length !== 0) { @@ -370,6 +395,8 @@ function * checkCreateAccess (authUser, subEntity) { let challengeDetails let resources + const challengeId = yield getV5ChallengeId(subEntity.challengeId) + // User can only create submission for themselves if (authUser.userId !== subEntity.memberId) { throw new errors.HttpStatusError(403, 'You are not allowed to submit on behalf of others') @@ -378,32 +405,46 @@ function * checkCreateAccess (authUser, subEntity) { const token = yield getM2Mtoken() try { - logger.info(`Calling to challenge API for fetch phases and winners for ${subEntity.challengeId}`) - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${subEntity.challengeId}`) + logger.info(`Calling to challenge API for fetch phases and winners for ${challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') - logger.info(`returned for ${subEntity.challengeId} with ${JSON.stringify(challengeDetails)}`) + logger.info(`returned for ${challengeId} with ${JSON.stringify(challengeDetails)}`) } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}?filter=id=${subEntity.challengeId}`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.error(ex) - throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${subEntity.challengeId}`) + throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${challengeId}`) } try { - resources = yield request.get(`${config.CHALLENGEAPI_URL}/${subEntity.challengeId}/resources`) + resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}/${subEntity.challengeId}/resources`) + logger.error(`Error while accessing ${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${challengeId}`) logger.error(ex) + throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${challengeId}`) + } + + // Get map of role id to role name + const resourceRolesMap = yield getRoleIdToRoleNameMap() + + // Check if role id to role name mapping is available. If not user's role cannot be determined. + if (resourceRolesMap == null || _.size(resourceRolesMap) === 0) { throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${subEntity.challengeId}`) } if (resources && challengeDetails) { - const currUserRoles = _.filter(resources.body.result.content, { properties: { Handle: authUser.handle } }) + const currUserRoles = _.filter(resources.body, { memberHandle: authUser.handle }) + + // Populate the role names for the current user role ids + _.forEach(currUserRoles, currentUserRole => { + currentUserRole.role = resourceRolesMap[currentUserRole.roleId] + }) + // Get phases and winner detail from challengeDetails - const phases = challengeDetails.body.result.content[0].allPhases - const winner = challengeDetails.body.result.content[0].winners + const phases = challengeDetails.body.phases + const winner = challengeDetails.body.winners // Check if the User is registered for the contest const submitters = _.filter(currUserRoles, { role: 'Submitter' }) @@ -419,7 +460,7 @@ function * checkCreateAccess (authUser, subEntity) { const currPhase = _.filter(phases, { id: submissionPhaseId }) - if (currPhase[0].phaseType === 'Final Fix') { + if (currPhase[0].name === 'Final Fix') { if (!authUser.handle.equals(winner[0].handle)) { throw new errors.HttpStatusError(403, 'Only winner is allowed to submit during Final Fix phase') } @@ -446,32 +487,46 @@ function * checkGetAccess (authUser, submission) { } const token = yield getM2Mtoken() + const challengeId = yield getV5ChallengeId(submission.challengeId) try { - resources = yield request.get(`${config.CHALLENGEAPI_URL}/${submission.challengeId}/resources`) + resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}/${submission.challengeId}/resources`) + logger.error(`Error while accessing ${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${challengeId}`) logger.error(ex) - throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${submission.challengeId}`) + throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${challengeId}`) } try { - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.error(ex) - throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${submission.challengeId}`) + throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${challengeId}`) + } + + // Get map of role id to role name + const resourceRolesMap = yield getRoleIdToRoleNameMap() + + // Check if role id to role name mapping is available. If not user's role cannot be determined. + if (resourceRolesMap == null || _.size(resourceRolesMap) === 0) { + throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${challengeId}`) } if (resources && challengeDetails) { // Fetch all roles of the User pertaining to the current challenge - const currUserRoles = _.filter(resources.body.result.content, { properties: { Handle: authUser.handle } }) - const subTrack = challengeDetails.body.result.content[0].subTrack - const phases = challengeDetails.body.result.content[0].allPhases + const currUserRoles = _.filter(resources.body, { memberHandle: authUser.handle }) + + // Populate the role names for the current user role ids + _.forEach(currUserRoles, currentUserRole => { + currentUserRole.role = resourceRolesMap[currentUserRole.roleId] + }) + + const subTrack = challengeDetails.body.legacy.subTrack // Check if the User is a Copilot const copilot = _.filter(currUserRoles, { role: 'Copilot' }) @@ -494,18 +549,18 @@ function * checkGetAccess (authUser, submission) { // User is either a Reviewer or Screener if (screener.length !== 0 || reviewer.length !== 0) { - const screeningPhase = _.filter(phases, { phaseType: 'Screening', phaseStatus: 'Scheduled' }) - const reviewPhase = _.filter(phases, { phaseType: 'Review', phaseStatus: 'Scheduled' }) + const screeningPhaseStatus = getPhaseStatus('Screening', challengeDetails.body) + const reviewPhaseStatus = getPhaseStatus('Review', challengeDetails.body) // Neither Screening Nor Review is Opened / Closed - if (screeningPhase.length !== 0 && reviewPhase.length !== 0) { + if (screeningPhaseStatus === 'Scheduled' && reviewPhaseStatus === 'Scheduled') { throw new errors.HttpStatusError(403, 'You can access the submission only when Screening / Review is open') } } else { - const appealsResponse = _.filter(phases, { phaseType: 'Appeals Response', phaseStatus: 'Closed' }) + const appealsResponseStatus = getPhaseStatus('Appeals Response', challengeDetails.body) // Appeals Response is not closed yet - if (appealsResponse.length === 0) { + if (appealsResponseStatus !== 'Closed') { throw new errors.HttpStatusError(403, 'You cannot access other submissions before the end of Appeals Response phase') } else { const userSubmission = yield fetchFromES({ @@ -543,30 +598,30 @@ function * checkGetAccess (authUser, submission) { function * checkReviewGetAccess (authUser, submission) { let challengeDetails const token = yield getM2Mtoken() + const challengeId = yield getV5ChallengeId(submission.challengeId) try { - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.error(ex) return false } if (challengeDetails) { - const subTrack = challengeDetails.body.result.content[0].subTrack - const phases = challengeDetails.body.result.content[0].allPhases + const subTrack = challengeDetails.body.legacy.subTrack // For Marathon Match, everyone can access review result if (subTrack === 'DEVELOP_MARATHON_MATCH') { logger.info('No access check for Marathon match') return true } else { - const appealsResponse = _.filter(phases, { phaseType: 'Appeals Response', phaseStatus: 'Closed' }) + const appealsResponseStatus = getPhaseStatus('Appeals Response', challengeDetails.body) // Appeals Response is not closed yet - if (appealsResponse.length === 0) { + if (appealsResponseStatus !== 'Closed') { throw new errors.HttpStatusError(403, 'You cannot access the review before the end of the Appeals Response phase') } @@ -642,6 +697,77 @@ function cleanseReviews (reviews, authUser) { return reviews } +/** + * Function to get role id to role name map + * @returns {Object|null} map + */ +function * getRoleIdToRoleNameMap () { + let resourceRoles + let resourceRolesMap = null + const token = yield getM2Mtoken() + try { + resourceRoles = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resource-roles`) + .set('Authorization', `Bearer ${token}`) + .set('Content-Type', 'application/json') + } catch (ex) { + logger.error(`Error while accessing ${config.RESOURCEAPI_V5_BASE_URL}/resource-roles`) + logger.error(ex) + resourceRoles = null + } + if (resourceRoles) { + resourceRolesMap = {} + _.forEach(resourceRoles.body, resourceRole => { + resourceRolesMap[resourceRole.id] = resourceRole.name + }) + } + return resourceRolesMap +} + +/** + * Function to get phase status of phases used in an active challenge + * @param {String} phaseName the phase name for retrieving status + * @param {Object} challengeDetails the challenge details + * @returns {('Scheduled' | 'Open' | 'Closed' | 'Invalid')} status of the phase + */ +function getPhaseStatus (phaseName, challengeDetails) { + const { phases } = challengeDetails + const queriedPhaseIndex = _.findIndex(phases, phase => { + return phase.name === phaseName + }) + // Requested phase name could not be found in phases hence 'Invalid' + if (queriedPhaseIndex === -1) { + return 'Invalid' + } + // If requested phase name is open return 'Open' + if (phases[queriedPhaseIndex].isOpen) { + return 'Open' + } else { + const { actualEndDate } = phases[queriedPhaseIndex] + if (!_.isEmpty(actualEndDate)) { + const present = new Date().getTime() + const actualDate = new Date(actualEndDate).getTime() + if (present > actualDate) { + return 'Closed' + } else { + return 'Scheduled' + } + } else { + return 'Scheduled' + } + } +} + +/** + * Change challengeId to v5ChallengeId and legacyChallengeId to challengeId + * @param {Object} submission + */ +function adjustSubmissionChallengeId (submission) { + if (submission.challengeId && submission.legacyChallengeId) { + submission.v5ChallengeId = submission.challengeId + submission.challengeId = submission.legacyChallengeId + } +} + module.exports = { wrapExpress, autoWrapExpress, @@ -656,5 +782,8 @@ module.exports = { checkReviewGetAccess, downloadFile, postToBusApi, - cleanseReviews + cleanseReviews, + getRoleIdToRoleNameMap, + getV5ChallengeId, + adjustSubmissionChallengeId } diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index efa4a4e9..d8b2ad93 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -154,6 +154,7 @@ function * getSubmission (authUser, submissionId) { // Return the retrieved submission logger.info(`getSubmission: returning data for submissionId: ${submissionId}`) + helper.adjustSubmissionChallengeId(submissionRecord) return submissionRecord } @@ -194,6 +195,7 @@ function * listSubmissions (authUser, query) { if (submission.review) { submission.review = helper.cleanseReviews(submission.review, authUser) } + helper.adjustSubmissionChallengeId(submission) return submission }) return data @@ -270,8 +272,8 @@ function * createSubmission (authUser, files, entity) { // Submission api only works with legacy challenge id // If it is a v5 challenge id, get the associated legacy challenge id - const challengeId = yield helper.getLegacyChallengeId(entity.challengeId) - + const challengeId = yield helper.getV5ChallengeId(entity.challengeId) + const legacyChallengeId = yield helper.getLegacyChallengeId(entity.challengeId) const currDate = (new Date()).toISOString() const item = { @@ -279,7 +281,8 @@ function * createSubmission (authUser, files, entity) { type: entity.type, url: url, memberId: entity.memberId, - challengeId: challengeId, + challengeId, + legacyChallengeId, created: currDate, updated: currDate, createdBy: authUser.handle || authUser.sub, @@ -297,7 +300,7 @@ function * createSubmission (authUser, files, entity) { if (entity.submissionPhaseId) { item.submissionPhaseId = entity.submissionPhaseId } else { - item.submissionPhaseId = yield helper.getSubmissionPhaseId(challengeId) + item.submissionPhaseId = yield helper.getSubmissionPhaseId(entity.challengeId) } if (entity.fileType) { @@ -329,6 +332,9 @@ function * createSubmission (authUser, files, entity) { logger.info('Prepared submission item to insert into Dynamodb. Inserting...') yield dbhelper.insertRecord(record) + // After save to db, adjust challengeId to busApi and response + helper.adjustSubmissionChallengeId(item) + // Push Submission created event to Bus API // Request body for Posting to Bus API const reqBody = { @@ -391,13 +397,13 @@ function * _updateSubmission (authUser, submissionId, entity) { throw new errors.HttpStatusError(404, `Submission with ID = ${submissionId} is not found`) } + const currDate = (new Date()).toISOString() + let challengeId = exist.challengeId + let legacyChallengeId = exist.legacyChallengeId if (entity.challengeId) { - // Submission api only works with legacy challenge id - // If it is a v5 challenge id, get the associated legacy challenge id - entity.challengeId = yield helper.getLegacyChallengeId(entity.challengeId) + challengeId = yield helper.getV5ChallengeId(entity.challengeId) + legacyChallengeId = yield helper.getLegacyChallengeId(entity.challengeId) } - - const currDate = (new Date()).toISOString() // Record used for updating in Database const record = { TableName: table, @@ -405,12 +411,13 @@ function * _updateSubmission (authUser, submissionId, entity) { id: submissionId }, UpdateExpression: `set #type = :t, #url = :u, memberId = :m, challengeId = :c, - updated = :ua, updatedBy = :ub, submittedDate = :sb`, + legacyChallengeId = :lc, updated = :ua, updatedBy = :ub, submittedDate = :sb`, ExpressionAttributeValues: { ':t': entity.type || exist.type, ':u': entity.url || exist.url, ':m': entity.memberId || exist.memberId, - ':c': entity.challengeId || exist.challengeId, + ':c': challengeId, + ':lc': legacyChallengeId, ':ua': currDate, ':ub': authUser.handle || authUser.sub, ':sb': entity.submittedDate || exist.submittedDate || exist.created @@ -444,6 +451,7 @@ function * _updateSubmission (authUser, submissionId, entity) { yield dbhelper.updateRecord(record) const updatedSub = yield _getSubmission(submissionId) + helper.adjustSubmissionChallengeId(updatedSub) // Push Submission updated event to Bus API // Request body for Posting to Bus API const reqBody = { @@ -455,6 +463,7 @@ function * _updateSubmission (authUser, submissionId, entity) { resource: helper.camelize(table), id: submissionId, challengeId: updatedSub.challengeId, + v5ChallengeId: updatedSub.v5ChallengeId, memberId: updatedSub.memberId, submissionPhaseId: updatedSub.submissionPhaseId, type: updatedSub.type, @@ -473,7 +482,9 @@ function * _updateSubmission (authUser, submissionId, entity) { { updated: currDate, updatedBy: authUser.handle || authUser.sub, - submittedDate: updatedSub.submittedDate + submittedDate: updatedSub.submittedDate, + challengeId: updatedSub.challengeId, + v5ChallengeId: updatedSub.v5ChallengeId } ) } diff --git a/test/common/testData.js b/test/common/testData.js index 2a2bb39f..fdeddb2a 100644 --- a/test/common/testData.js +++ b/test/common/testData.js @@ -121,19 +121,63 @@ const testReviewTypesES = { const nonExSubmissionId = 'b3564180-65aa-42ec-a945-5fd21dec0502' +const testChallengeResources = [ + { + 'id': '9a06daeb-1b8e-4d91-9bd4-c5fda7c93db2', + 'challengeId': '9131c5da-6ed9-4186-9a1b-4de31df5ba17', + 'memberId': '88774396', + 'memberHandle': 'Sharathkumar92', + 'roleId': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bd', + 'created': '2021-02-02T22:51:59.000Z', + 'createdBy': 'jmgasper' + }, + { + 'id': '9a06daeb-1b8e-4d91-9bd4-c5fda7c93db2', + 'challengeId': '9131c5da-6ed9-4186-9a1b-4de31df5ba17', + 'memberId': '88774396', + 'memberHandle': 'Sharathkumar92', + 'roleId': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bb', + 'created': '2021-02-02T22:51:59.000Z', + 'createdBy': 'jmgasper' + } +] + +const testResourceRoles = [ + { + 'id': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bd', + 'name': 'Copilot', + 'legacyId': 14, + 'fullReadAccess': true, + 'fullWriteAccess': true, + 'isActive': true, + 'selfObtainable': false + }, + { + 'id': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bb', + 'name': 'Submitter', + 'legacyId': 14, + 'fullReadAccess': true, + 'fullWriteAccess': true, + 'isActive': true, + 'selfObtainable': false + } +] + const testSubmission = { Item: { challengeId: 'c3564180-65aa-42ec-a945-5fd21dec0502', id: 'a12a4180-65aa-42ec-a945-5fd21dec0501', type: 'ContestSubmission', url: 'https://software.topcoder.com/review/actions/DownloadContestSubmission?uid=123456', - memberId: 'b24d4180-65aa-42ec-a945-5fd21dec0501', + memberId: 40493050, legacySubmissionId: 'b24d4180-65aa-42ec-a945-5fd21dec0501', submissionPhaseId: 764567, created: '2018-05-20T07:00:30.123Z', createdBy: 'topcoder user', updated: '2018-06-01T07:36:28.178Z', - updatedBy: 'topcoder user' + updatedBy: 'topcoder user', + review: [], + reviewSummation: [] } } @@ -385,6 +429,7 @@ const testReview = { id: 'd24d4180-65aa-42ec-a945-5fd21dec0502', score: 92, reviewerId: 'c23a4180-65aa-42ec-a945-5fd21dec0503', + reviewedDate: '2021-02-02T11:39:38.685Z', submissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0501', scoreCardId: 123456789, status: 'queued', @@ -401,6 +446,7 @@ const testReviewPatch = { id: 'd24d4180-65aa-42ec-a945-5fd21dec0502', score: 90, reviewerId: 'c23a4180-65aa-42ec-a945-5fd21dec0503', + reviewedDate: '2021-02-02T11:39:38.685Z', submissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0501', scoreCardId: 123456789, status: 'queued', @@ -535,6 +581,7 @@ const testReviewSummation = { aggregateScore: 99, isPassing: true, submissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0501', + reviewedDate: '2021-02-02T11:39:38.685Z', scoreCardId: 123456789, created: '2018-05-20T07:00:30.123Z', updated: '2018-06-01T07:36:28.178Z', @@ -549,6 +596,7 @@ const testReviewSummationPatch = { aggregateScore: 78.5, isPassing: false, submissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0501', + reviewedDate: '2021-02-02T11:39:38.685Z', scoreCardId: 123456789, created: '2018-05-20T07:00:30.123Z', updated: '2018-06-01T07:36:28.178Z', @@ -664,98 +712,180 @@ const testReviewSummationsES = { } const testChallengeAPIResponse = { - id: '24a97f2f:1655fef5034:-7568', - result: { - success: true, - status: 200, - metadata: { - fields: null, - totalCount: 5 + 'id': '77eb9522-ea41-4334-974d-7604097d23e7', + 'created': '2020-11-02T21:34:19Z', + 'createdBy': 'tcwebservice', + 'updated': '2020-12-28T06:44:27Z', + 'updatedBy': 'AutoPilot', + 'status': 'Active', + 'projectId': 16661, + 'name': 'TCO Leaderboard Test 3', + 'typeId': '927abff4-7af9-4145-8ba1-577c16e64e2e', + 'trackId': '9b6fc876-f4d9-4ccb-9dfd-419247628825', + 'startDate': '2020-12-21T18:24:09Z', + 'legacy': { + 'reviewType': 'COMMUNITY', + 'isTask': false, + 'subTrack': 'CODE', + 'directProjectId': 23741, + 'track': 'DEVELOP', + 'reviewScorecardId': 30001610, + 'forumId': 0 + }, + 'descriptionFormat': 'HTML', + 'timelineTemplateId': '7ebf1c69-f62f-4d3a-bdfb-fe9ddb56861c', + 'terms': [ + { + 'roleId': '732339e7-8e30-49d7-9198-cccf9451e221', + 'id': 'b11da5cd-713f-478d-90f4-f679ef53ee95' + }, + { + 'roleId': '3eedd4a4-3c68-4f68-8de4-a1ca5c2055e5', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' + }, + { + 'roleId': '318b9c07-079a-42d9-a81f-b96be1dc1099', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' }, - content: [ - { - challengeId: 30049360, - id: 733195, - phaseType: 'Registration', - phaseStatus: 'Open', - scheduledStartTime: '1438002000000', - scheduledEndTime: '2019-12-02T09:00:00Z', - actualStartTime: '1438002000000', - actualEndTime: null, - fixedStartTime: '1438002000000', - duration: 137293200000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - }, - { - challengeId: 30049360, - id: 733196, - phaseType: 'Submission', - phaseStatus: 'Open', - scheduledStartTime: '1438002300000', - scheduledEndTime: '2019-12-02T09:00:00Z', - actualStartTime: null, - actualEndTime: null, - fixedStartTime: null, - duration: 137292900000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - }, - { - challengeId: 30049360, - id: 733197, - phaseType: 'Review', - phaseStatus: 'Scheduled', - scheduledStartTime: '1575295200000', - scheduledEndTime: '2019-12-04T09:00:00Z', - actualStartTime: null, - actualEndTime: null, - fixedStartTime: null, - duration: 172800000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - }, - { - challengeId: 30049360, - id: 733198, - phaseType: 'Appeals', - phaseStatus: 'Scheduled', - scheduledStartTime: '1575468000000', - scheduledEndTime: '2019-12-05T09:00:00Z', - actualStartTime: null, - actualEndTime: null, - fixedStartTime: null, - duration: 86400000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - }, - { - challengeId: 30049360, - id: 733199, - phaseType: 'Appeals Response', - phaseStatus: 'Scheduled', - scheduledStartTime: '1575554400000', - scheduledEndTime: '2019-12-05T09:00:00Z', - actualStartTime: null, - actualEndTime: null, - fixedStartTime: null, - duration: 43200000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - } - ] + { + 'roleId': 'ff556573-5da6-4392-b38c-08c1d7599c4a', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' + }, + { + 'roleId': 'e0544b94-6420-4afc-8f63-238eddc751b9', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' + }, + { + 'roleId': '0e9c6879-39e4-4eb6-b8df-92407890faf1', + 'id': '75d2f6bb-aadc-475e-9728-32c1dbd13655' + }, + { + 'roleId': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bd', + 'id': 'e0993b1a-abf7-45e6-8ed9-8cd0546be90b' + }, + { + 'roleId': 'd663fc84-5c37-43d1-a537-793feffb7667', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' + } + ], + 'phases': [ + { + 'duration': 561600, + 'scheduledEndDate': '2020-12-28T06:44:27Z', + 'actualEndDate': '2020-12-28T06:44:27Z', + 'isOpen': false, + 'name': 'Registration', + 'phaseId': 'a93544bc-c165-4af4-b55e-18f3593b457a', + 'actualStartDate': '2020-12-21T18:24:09Z', + 'id': 'f6166029-cdef-4b72-b7a4-f2d3074bafac', + 'scheduledStartDate': '2020-12-21T18:24:09Z' + }, + { + 'duration': 561300, + 'scheduledEndDate': '2020-12-28T06:44:28Z', + 'actualEndDate': '2020-12-28T06:44:28Z', + 'isOpen': true, + 'name': 'Submission', + 'phaseId': '6950164f-3c5e-4bdc-abc8-22aaf5a1bd49', + 'actualStartDate': '2020-12-21T18:44:58Z', + 'id': '90ddb27a-cc49-454c-8367-354011eeba73', + 'scheduledStartDate': '2020-12-21T18:44:58Z' + }, + { + 'duration': 172800, + 'scheduledEndDate': '2020-12-30T06:44:00Z', + 'actualEndDate': '2020-12-28T06:51:27Z', + 'isOpen': false, + 'name': 'Review', + 'phaseId': 'aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b', + 'actualStartDate': '2020-12-28T06:51:27Z', + 'id': '35a75a3f-c9ba-46ad-8003-f605c9bb4791', + 'scheduledStartDate': '2020-12-28T06:44:28Z' + }, + { + 'duration': 86400, + 'scheduledEndDate': '2020-12-31T06:44:00Z', + 'actualEndDate': '2020-12-28T06:51:27Z', + 'isOpen': false, + 'name': 'Appeals', + 'phaseId': '1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6', + 'actualStartDate': '2020-12-28T06:51:27Z', + 'id': '3d16078a-2362-41fe-af82-01112b8f27c8', + 'scheduledStartDate': '2020-12-30T06:44:00Z' + }, + { + 'duration': 43200, + 'scheduledEndDate': '2020-12-31T18:44:00Z', + 'actualEndDate': '2020-12-28T06:51:27Z', + 'isOpen': false, + 'name': 'Appeals Response', + 'phaseId': '797a6af7-cd3f-4436-9fca-9679f773bee9', + 'actualStartDate': '2020-12-28T06:51:27Z', + 'id': '2359d4fd-aa1a-4403-98c5-a1f841b8062e', + 'scheduledStartDate': '2020-12-31T06:44:00Z' + }, + { + 'duration': 86400, + 'scheduledEndDate': '2020-12-29T06:48:00Z', + 'actualEndDate': '2020-12-28T06:51:27Z', + 'isOpen': true, + 'name': 'Post-Mortem', + 'phaseId': 'f308bdb4-d3da-43d8-942b-134dfbaf5c45', + 'actualStartDate': '2020-12-28T06:48:44Z', + 'id': '3a579100-e334-4b8f-ac89-7c8c696d42f0', + 'scheduledStartDate': '2020-12-28T06:48:44Z' + } + ], + 'discussions': [ + { + 'provider': 'vanilla', + 'name': 'TCO Leaderboard Test 3 Discussion', + 'id': 'cfbb21e8-a67a-4a23-997c-04022894d958', + 'type': 'challenge', + 'url': 'https://vanilla.topcoder-dev.com/categories/77eb9522-ea41-4334-974d-7604097d23e7' + } + ], + 'description': 'test', + 'groups': [], + 'endDate': '2020-12-29T06:48:00Z', + 'numOfSubmissions': 0, + 'numOfRegistrants': 0, + 'currentPhaseNames': [ + 'Post-Mortem' + ], + 'registrationStartDate': '2020-12-21T18:24:09Z', + 'registrationEndDate': '2020-12-28T06:44:27Z', + 'submissionStartDate': '2020-12-21T18:44:58Z', + 'submissionEndDate': '2020-12-28T06:44:28Z', + 'track': 'Development', + 'type': 'Challenge', + 'attachments': [], + 'prizeSets': [ + { + 'prizes': [ + { + 'type': 'USD', + 'value': 1 + } + ], + 'description': 'Challenge Prizes', + 'type': 'placement' + } + ], + 'tags': [ + 'Automated Testing' + ], + 'legacyId': 30057477, + 'metadata': [], + 'events': [], + 'task': { + 'isAssigned': false, + 'isTask': false, + 'memberId': null }, - version: 'v4' + 'overview': { + 'totalPrizes': 1 + } } module.exports = { @@ -780,5 +910,7 @@ module.exports = { testReviewSummationPatch, testReviewSummationES, testReviewSummationsES, - testChallengeAPIResponse + testChallengeAPIResponse, + testResourceRoles, + testChallengeResources } diff --git a/test/unit/ReviewService.test.js b/test/unit/ReviewService.test.js index 62181110..b7e3d71a 100644 --- a/test/unit/ReviewService.test.js +++ b/test/unit/ReviewService.test.js @@ -75,8 +75,7 @@ describe('Review Service tests', () => { .get(`${config.API_VERSION}/reviews/${testReview.Item.id}`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) .end((err, res) => { - res.should.have.status(200) - res.body.should.have.all.keys(Object.keys(testReview.Item)) + res.body.should.have.all.keys(Object.keys(_.omit(testReview.Item, ['reviewedDate']))) res.body.id.should.be.eql(testReview.Item.id) res.body.score.should.be.eql(testReview.Item.score) res.body.reviewerId.should.be.eql(testReview.Item.reviewerId) @@ -99,7 +98,7 @@ describe('Review Service tests', () => { .send({}) .end((err, res) => { res.should.have.status(400) - res.body.message.should.be.eql('"score" is required') + res.body.message.should.be.eql('"typeId" is required') done() }) }) diff --git a/test/unit/ReviewSummationService.test.js b/test/unit/ReviewSummationService.test.js index af8f0213..ee9a7311 100644 --- a/test/unit/ReviewSummationService.test.js +++ b/test/unit/ReviewSummationService.test.js @@ -76,7 +76,7 @@ describe('Review Summation Service tests', () => { .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) .end((err, res) => { res.should.have.status(200) - res.body.should.have.all.keys(Object.keys(testReviewSummation.Item)) + res.body.should.have.all.keys(Object.keys(_.omit(testReviewSummation.Item, ['reviewedDate']))) res.body.id.should.be.eql(testReviewSummation.Item.id) res.body.aggregateScore.should.be.eql(testReviewSummation.Item.aggregateScore) res.body.submissionId.should.be.eql(testReviewSummation.Item.submissionId) diff --git a/test/unit/ReviewTypeService.test.js b/test/unit/ReviewTypeService.test.js index 8777aa65..922f519c 100644 --- a/test/unit/ReviewTypeService.test.js +++ b/test/unit/ReviewTypeService.test.js @@ -429,13 +429,12 @@ describe('ReviewType Service tests', () => { }) }) - it('Getting review types with user token should throw 403', (done) => { + it('Getting review types with user token should return 200', (done) => { chai.request(app) .get(`${config.API_VERSION}/reviewTypes`) .set('Authorization', `Bearer ${config.USER_TOKEN}`) .end((err, res) => { - res.should.have.status(403) - res.body.message.should.be.eql('You are not allowed to perform this action!') + res.should.have.status(200) done() }) }) diff --git a/test/unit/SubmissionService.test.js b/test/unit/SubmissionService.test.js index 9e92daaf..652e0cba 100644 --- a/test/unit/SubmissionService.test.js +++ b/test/unit/SubmissionService.test.js @@ -14,7 +14,7 @@ const chaiHttp = require('chai-http') const should = chai.should() // eslint-disable-line const app = require('../../app') const { - nonExSubmissionId, testSubmission, testSubmissionWoLegacy, + nonExSubmissionId, testSubmission, testSubmissionPatch } = require('../common/testData') @@ -65,7 +65,7 @@ describe('Submission Service tests', () => { .set('Authorization', `Bearer ${config.USER_TOKEN}`) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(testSubmission.Item)) + res.body.should.have.keys(Object.keys(_.omit(testSubmission.Item, ['submittedDate']))) res.body.id.should.be.eql(testSubmission.Item.id) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -80,7 +80,7 @@ describe('Submission Service tests', () => { .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) .end((err, res) => { res.should.have.status(200) - res.body.should.have.all.keys(Object.keys(testSubmission.Item)) + res.body.should.have.all.keys(Object.keys(_.omit(testSubmission.Item, ['submittedDate']))) res.body.id.should.be.eql(testSubmission.Item.id) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -135,7 +135,7 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.USER_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'url', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'url', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(400) res.body.message.should.be.eql('Either file to be uploaded or URL should be present') @@ -163,9 +163,9 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.USER_TOKEN}`) - .field('challengeId', testSubmissionWoLegacy.Item.challengeId) - .field('type', testSubmissionWoLegacy.Item.type) - .field('memberId', testSubmissionWoLegacy.Item.memberId) + .field('challengeId', testSubmission.Item.challengeId) + .field('type', testSubmission.Item.type) + .field('memberId', testSubmission.Item.memberId) .field('fileType', 'pdf') .attach('submission', './test/common/fileToUpload.zip', 'fileToUpload.zip') .end((err, res) => { @@ -179,21 +179,21 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.USER_TOKEN}`) - .field('challengeId', testSubmissionWoLegacy.Item.challengeId) - .field('type', testSubmissionWoLegacy.Item.type) - .field('memberId', testSubmissionWoLegacy.Item.memberId) + .field('challengeId', testSubmission.Item.challengeId) + .field('type', testSubmission.Item.type) + .field('memberId', testSubmission.Item.memberId) .field('fileType', 'zip') .attach('submission', './test/common/fileToUpload.zip', 'fileToUpload.zip') .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip', submissionPhaseId: 733196 }, testSubmissionWoLegacy.Item))) + res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip', submissionPhaseId: 733196, submittedDate: '2018-05-20T07:00:30.123Z' }, _.omit(testSubmission.Item, ['legacySubmissionId', 'review', 'reviewSummation'])))) res.body.id.should.not.be.eql(null) - res.body.challengeId.should.be.eql(testSubmissionWoLegacy.Item.challengeId) - res.body.type.should.be.eql(testSubmissionWoLegacy.Item.type) + res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) + res.body.type.should.be.eql(testSubmission.Item.type) res.body.url.should.not.be.eql(null) - res.body.memberId.should.be.eql(testSubmissionWoLegacy.Item.memberId) + res.body.memberId.should.be.eql(testSubmission.Item.memberId) res.body.fileType.should.be.eql('zip') - res.body.submissionPhaseId.should.be.eql(733196) + res.body.submissionPhaseId.should.be.eql('90ddb27a-cc49-454c-8367-354011eeba73') done() }) }) @@ -216,10 +216,10 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip' }, testSubmission.Item))) + res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip', submissionPhaseId: 733196, submittedDate: '2018-05-20T07:00:30.123Z' }, _.omit(testSubmission.Item, ['review', 'reviewSummation'])))) res.body.id.should.not.be.eql(null) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -233,10 +233,10 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'submissionPhaseId'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'submissionPhaseId', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip' }, testSubmission.Item))) + res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip', submissionPhaseId: 733196, submittedDate: '2018-05-20T07:00:30.123Z' }, _.omit(testSubmission.Item, ['review', 'reviewSummation'])))) res.body.id.should.not.be.eql(null) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -316,7 +316,7 @@ describe('Submission Service tests', () => { chai.request(app) .put(`${config.API_VERSION}/submissions/${nonExSubmissionId}`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(404) res.body.message.should.be.eql(`Submission with ID = ${nonExSubmissionId} is not found`) @@ -328,10 +328,10 @@ describe('Submission Service tests', () => { chai.request(app) .put(`${config.API_VERSION}/submissions/${testSubmission.Item.id}`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(testSubmission.Item)) + res.body.should.have.keys(Object.keys(_.extend({ submissionPhaseId: 733196 }, _.omit(testSubmission.Item, ['review', 'reviewSummation', 'submittedDate'])))) res.body.id.should.not.be.eql(null) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -342,16 +342,16 @@ describe('Submission Service tests', () => { it('Updating submission without legacy fields with Admin token should get succeeded', (done) => { chai.request(app) - .put(`${config.API_VERSION}/submissions/${testSubmissionWoLegacy.Item.id}`) + .put(`${config.API_VERSION}/submissions/${testSubmission.Item.id}`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmissionWoLegacy.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(testSubmissionWoLegacy.Item)) + res.body.should.have.keys(Object.keys(_.extend({ submissionPhaseId: 733196 }, _.omit(testSubmission.Item, ['review', 'reviewSummation', 'submittedDate'])))) res.body.id.should.not.be.eql(null) - res.body.challengeId.should.be.eql(testSubmissionWoLegacy.Item.challengeId) - res.body.type.should.be.eql(testSubmissionWoLegacy.Item.type) - res.body.url.should.be.eql(testSubmissionWoLegacy.Item.url) + res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) + res.body.type.should.be.eql(testSubmission.Item.type) + res.body.url.should.be.eql(testSubmission.Item.url) done() }) }).timeout(10000) diff --git a/test/unit/prepare.js b/test/unit/prepare.js index 824420db..9a476250 100644 --- a/test/unit/prepare.js +++ b/test/unit/prepare.js @@ -34,6 +34,16 @@ prepare(function (done) { } }) + AWS.mock('DynamoDB.DocumentClient', 'query', (params, callback) => { + if (params.ExpressionAttributeValues[':p_submissionId'] === testData.nonExSubmissionId) { + callback(null, { + Count: 0 + }) + } else if (params.ExpressionAttributeValues[':p_submissionId'] === testData.testSubmission.Item.id) { + callback(null, []) + } + }) + AWS.mock('DynamoDB.DocumentClient', 'put', (params, callback) => { callback(null, {}) }) @@ -70,7 +80,9 @@ prepare(function (done) { // Mock Posting to Bus API and ES interactions const authUrl = URL.parse(config.AUTH0_URL) const busUrl = URL.parse(config.BUSAPI_EVENTS_URL) - const challengeApiUrl = URL.parse(`${config.CHALLENGEAPI_URL}/30049360/phases`) + const challengeApiUrl = URL.parse(`${config.CHALLENGEAPI_V5_URL}/c3564180-65aa-42ec-a945-5fd21dec0502`) + const resourcesApi = URL.parse(`${config.BUSAPI_URL}/resources?challengeId=c3564180-65aa-42ec-a945-5fd21dec0502`) + const resourceRolesApi = URL.parse(`${config.BUSAPI_URL}/resource-roles`) nock(/.com/) .persist() @@ -91,11 +103,15 @@ prepare(function (done) { return body }) .post(authUrl.path) - .reply(200, { access_token: 'test' }) + .reply(200, { access_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20nIiwiaWF0IjoxNjEyMjg0NDIyLCJleHAiOjE2MTIyODgyODEsInVzZXJJZCI6IjQwNDMzMjg4IiwiZW1haWwiOiJhZG1pbkB0b3Bjb2Rlci5jb20iLCJqdGkiOiJjM2FjNjA4YS01NmJlLTQ1ZDAtOGY2YS0zMWZlOTRiOTU2MWMiLCJyb2xlcyI6IltcIkFkbWluaXN0cmF0b3JcIl0iLCJoYW5kbGUiOiJUb255SiJ9.7MLAeTtAxS-RvQWA2fEoS2va7mOLd_n-COnDWzLVQ_s' }) .post(busUrl.path) .reply(204) .get(challengeApiUrl.path) .reply(200, testData.testChallengeAPIResponse) + .get(resourcesApi.path) + .reply(200, testData.testChallengeResources) + .get(resourceRolesApi.path) + .reply(200, testData.testResourceRoles) .post(`/${config.esConfig.ES_INDEX}/${config.esConfig.ES_TYPE}/_search`, 'reviewType') .query(true) .reply(200, testData.testReviewTypesES)