From baab7c04444ebb2383c217c27af1807c258b4402 Mon Sep 17 00:00:00 2001 From: wiki Date: Thu, 12 Nov 2015 15:11:26 -0500 Subject: [PATCH 01/11] production patches --- actions/docusign.js | 2 +- actions/user.js | 2 +- config/logger.js | 4 +-- initializers/dataAccess.js | 17 ++++++++--- initializers/helper.js | 2 +- package.json.org | 62 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 package.json.org diff --git a/actions/docusign.js b/actions/docusign.js index 572e8f95a..625d0cb69 100644 --- a/actions/docusign.js +++ b/actions/docusign.js @@ -310,7 +310,7 @@ exports.generateDocusignViewURL = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', - transaction: 'read', + transaction: 'write', cacheEnabled : false, databases: ["informixoltp", "common_oltp"], inputs: { diff --git a/actions/user.js b/actions/user.js index c8f297fe2..88bece40f 100644 --- a/actions/user.js +++ b/actions/user.js @@ -361,7 +361,7 @@ function getUserIdentityByAuth0Id(api, connection, next) { function (cb) { try { var splits = auth0id.split('|'); - if (splits[0] === 'ad') { + if (splits[0] === 'ad' || splits[0] === 'auth0') { cb(null, [{ user_id: Number(splits[1]) }]); } else { api.helper.getProviderId(splits[0], function (err, provider) { diff --git a/config/logger.js b/config/logger.js index 5d4991f1a..0812c59c3 100644 --- a/config/logger.js +++ b/config/logger.js @@ -12,7 +12,7 @@ exports.default = { logger.transports.push(function (api, winston) { return new (winston.transports.Console)({ colorize: true, - level: 'debug', + level: 'info', timestamp: api.utils.sqlDateTime, json: false }); @@ -31,7 +31,7 @@ exports.default = { logger.transports.push(function (api, winston) { return new (winston.transports.File)({ filename: api.config.general.paths.log[0] + '/actionhero-worker.log', - level: 'debug', + level: 'info', colorize: true, timestamp: api.utils.sqlDateTime, json: false diff --git a/initializers/dataAccess.js b/initializers/dataAccess.js index c49ad980c..9849555db 100644 --- a/initializers/dataAccess.js +++ b/initializers/dataAccess.js @@ -113,6 +113,11 @@ function parameterizeQuery(query, params, callback) { }); } +function isSafeToUseJavaBridge(sql) { + var lowerSQL = sql.toLowerCase(); + return lowerSQL.indexOf("insert") === -1 && lowerSQL.indexOf("update") === -1 && lowerSQL.indexOf("delete") === -1; +} + function executePreparedStatement(api, sql, parameters, connection, next, db) { async.waterfall([ function (cb) { @@ -120,7 +125,7 @@ function executePreparedStatement(api, sql, parameters, connection, next, db) { }, function (parametrizedQuery, cb) { sql = parametrizedQuery; - if (api.helper.readTransaction) { + if (isSafeToUseJavaBridge(sql) && api.helper.readTransaction) { api.log("Calling Java Bridge", "debug"); api.log(sql, "debug"); @@ -304,8 +309,13 @@ exports.dataAccess = function (api, next) { return; } - if (!api.helper.readTransaction) { + sql = queries[queryName].sql; + + if (!isSafeToUseJavaBridge(sql) || !api.helper.readTransaction) { connection = connectionMap[queries[queryName].db]; + api.log("######### MD #########", "info"); + api.log(JSON.stringify(connectionMap), "info"); + api.log(queryName, "info"); error = helper.checkObject(connection, "connection"); } @@ -314,7 +324,6 @@ exports.dataAccess = function (api, next) { return; } - sql = queries[queryName].sql; if (!sql) { api.log('Unregistered query ' + queryName + ' is asked for.', 'error'); next('The query for name ' + queryName + ' is not registered'); @@ -354,7 +363,7 @@ exports.dataAccess = function (api, next) { return; } - if (!api.helper.readTransaction) { + if (!isSafeToUseJavaBridge(sql) || !api.helper.readTransaction) { connection = connectionMap[dbName]; error = helper.checkObject(connection, "connection"); } diff --git a/initializers/helper.js b/initializers/helper.js index 2acc131d8..2e53f1cc6 100644 --- a/initializers/helper.js +++ b/initializers/helper.js @@ -1219,7 +1219,7 @@ helper.getProviderId = function (provider, callback) { if (provider.startsWith("salesforce")) { providerId = helper.socialProviders.salesforce; } - if (provider.startsWith("ad")) { + if (provider.startsWith("ad") || provider.startsWith("auth0")) { providerId = helper.socialProviders.ad; } if (providerId) { diff --git a/package.json.org b/package.json.org new file mode 100644 index 000000000..2306e2aeb --- /dev/null +++ b/package.json.org @@ -0,0 +1,62 @@ +{ + "author": "TCASSEMBLER", + "name": "tcapi", + "description": "", + "version": "0.0.1", + "homepage": "", + "repository": { + "type": "", + "url": "" + }, + "keywords": "", + "engines": { + "node": ">=0.8.0" + }, + "dependencies": { + "actionhero": "8.0.x", + "xml2js": "0.2.x", + "async": "0.2.x", + "underscore": "1.5.x", + "datejs": "0.0.x", + "string": "1.6.x", + "ldapjs": "0.7.x", + "nodemailer": "0.5.x", + "email-templates": "0.1.x", + "bcrypt": "0.7.x", + "bigdecimal": "0.6.x", + "bignum": "0.6.x", + "java": "0.3.x", + "informix-wrapper": "git://github.com/cloudspokes/informix-wrapper.git#469300dbd4913c5d467b6957bc4610d95c3923ed", + "forums-wrapper": "git://github.com/cloudspokes/forums-wrapper.git#12b57be495c2e10431173522bc9eff60e0575959", + "asn1": "*", + "crypto": "0.0.x", + "jsonwebtoken": "0.4.x", + "request": "~2.33.0", + "soap": "~0.3.2", + "moment-timezone": "0.0.x", + "moment": "~2.5.1", + "mime": "~1.2.11", + "xtend": "2.1.x", + "validator": "~3.5.0", + "adm-zip": "0.4.x", + "mkdirp": "0.3.x", + "archiver": "~0.6.1", + "redis": "0.10.x", + "temp": "0.7.0", + "ssha": "*" + }, + "devDependencies": { + "supertest": "0.8.x", + "mocha": "1.x", + "chai": "1.8.x", + "fakeredis": "0.1.x", + "should": ">= 3.0.0" + }, + "scripts": { + "start": "node ./node_modules/.bin/actionhero start &", + "test": "node_modules/.bin/mocha --recursive ./test", + "startCluster": "node ./node_modules/.bin/actionhero startCluster", + "actionHero": "node ./node_modules/.bin/actionhero", + "help": "node ./node_modules/.bin/actionhero help" + } +} From c7ee58b2dbe4e8754b02aff9b60d65f86cbbc10c Mon Sep 17 00:00:00 2001 From: Mauricio Desiderio Date: Mon, 27 Jun 2016 12:45:52 -0500 Subject: [PATCH 02/11] fixing challenge registration email for design challenges to have correct challenge submission url --- actions/challengeRegistration.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index 5b0c09319..242173025 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -382,7 +382,8 @@ var sendNotificationEmail = function (api, componentInfo, userId, activeForumCat documentationDetails = ''; // we need to set up a new environment variable for the web server name specifici to each environment //submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/'; - submitURL = 'https://www.topcoder.com/challenge-details/' + challengeId + '/submit/'; + submitURL = 'https://www.topcoder.com/challenges/' + challengeId + '/submit/file/'; + reviewURL = process.env.TC_SOFTWARE_SERVER_NAME + '/review'; if (componentInfo.phase_id === 112) { From abc60792aeb58301d87ece12b13af0a7fabeba36 Mon Sep 17 00:00:00 2001 From: ykohata Date: Tue, 26 Jul 2016 13:35:51 +0900 Subject: [PATCH 03/11] Fix for the issue: Do not allow a member to register for challenge if country is not set https://app.asana.com/0/152805928309317/156182574694106 --- initializers/challengeHelper.js | 6 ++++++ queries/challenge_registration_validations | 3 +++ 2 files changed, 9 insertions(+) diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index 2c8b7e6d1..1a0d16748 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -161,6 +161,12 @@ exports.challengeHelper = function (api, next) { return; } + // Do not allow a member to register for challenge if country is not set + if (rows[0].comp_country_is_null) { + cb(new ForbiddenError('You cannot participate in this challenge as your country is not set.')); + return; + } + if (rows[0].project_category_id === COPILOT_POSTING_PROJECT_TYPE) { if (!rows[0].user_is_copilot && rows[0].copilot_type && rows[0].copilot_type.indexOf("Marathon Match") < 0) { cb(new ForbiddenError('You cannot participate in this challenge because you are not an active member of the copilot pool.')); diff --git a/queries/challenge_registration_validations b/queries/challenge_registration_validations index 874e26b32..298d7888c 100644 --- a/queries/challenge_registration_validations +++ b/queries/challenge_registration_validations @@ -7,6 +7,7 @@ select (ce.contest_eligibility_id IS NULL) as no_elgibility_req, (ugx.login_id IS NOT NULL) as user_in_eligible_group, (uax.user_id IS NOT NULL OR coder.coder_id IS NOT NULL) as user_country_banned, + (coder2.comp_country_code IS NULL) as comp_country_is_null, (cop.copilot_profile_id IS NOT NULL) as user_is_copilot, (pctl.name) as copilot_type from project p @@ -50,6 +51,8 @@ left outer join ( on coder.comp_country_code=country.country_code and country.country_name in ( "Iran", "North Korea", "Cuba", "Sudan", "Syria" ) ) on coder.coder_id = @userId@ +-- Get coder to check comp_country_code +left outer join informixoltp:coder coder2 on coder2.coder_id = @userId@ -- Get the copilot type left join ( project_copilot_type pct join project_copilot_type_lu pctl From dff6242efbda3529316964df98b47e8af1eb2e56 Mon Sep 17 00:00:00 2001 From: ykohata Date: Wed, 3 Aug 2016 14:00:49 +0900 Subject: [PATCH 04/11] Fix for the issue: Do not allow a member to register for challenge if country is not set (https://app.asana.com/0/152805928309317/156182574694106) - Added check for the case that the competition country is empty - Modified error messages --- initializers/challengeHelper.js | 4 ++-- queries/challenge_registration_validations | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index 1a0d16748..2622da0b4 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -157,13 +157,13 @@ exports.challengeHelper = function (api, next) { } if (rows[0].user_country_banned) { - cb(new ForbiddenError('You cannot participate in this challenge as your country is banned.')); + cb(new ForbiddenError('You are not eligible to participate in this challenge because of your country of residence. Please see our terms of service for more information.')); return; } // Do not allow a member to register for challenge if country is not set if (rows[0].comp_country_is_null) { - cb(new ForbiddenError('You cannot participate in this challenge as your country is not set.')); + cb(new ForbiddenError('You are not eligible to participate in this challenge because you have not specified your country of residence. Please go to your Settings and enter a country. Please see our terms of service for more information.')); return; } diff --git a/queries/challenge_registration_validations b/queries/challenge_registration_validations index 298d7888c..29fa1a4e8 100644 --- a/queries/challenge_registration_validations +++ b/queries/challenge_registration_validations @@ -7,7 +7,7 @@ select (ce.contest_eligibility_id IS NULL) as no_elgibility_req, (ugx.login_id IS NOT NULL) as user_in_eligible_group, (uax.user_id IS NOT NULL OR coder.coder_id IS NOT NULL) as user_country_banned, - (coder2.comp_country_code IS NULL) as comp_country_is_null, + (coder2.comp_country_code IS NULL OR coder2.comp_country_code = '') as comp_country_is_null, (cop.copilot_profile_id IS NOT NULL) as user_is_copilot, (pctl.name) as copilot_type from project p From 093f916d14bbc4114786a223bf0b0ae79f15f655 Mon Sep 17 00:00:00 2001 From: ajefts Date: Wed, 3 Aug 2016 15:38:56 -0400 Subject: [PATCH 05/11] Update challengeRegistration.js --- actions/challengeRegistration.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index 242173025..e9ac4260d 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -382,7 +382,8 @@ var sendNotificationEmail = function (api, componentInfo, userId, activeForumCat documentationDetails = ''; // we need to set up a new environment variable for the web server name specifici to each environment //submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/'; - submitURL = 'https://www.topcoder.com/challenges/' + challengeId + '/submit/file/'; + // Set the default submission URL. + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenges/' + challengeId + '/submit/file/'; reviewURL = process.env.TC_SOFTWARE_SERVER_NAME + '/review'; @@ -400,9 +401,11 @@ var sendNotificationEmail = function (api, componentInfo, userId, activeForumCat if (challengeType === CHALLENGE_TYPE.DEVELOP) { forumURL = api.config.tcConfig.developForumsUrlPrefix + activeForumCategoryId; reviewURL = process.env.TC_SOFTWARE_SERVER_NAME + '/review/actions/ViewProjectDetails?pid=' + challengeId; + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenges/' + challengeId + '/submit/file/'; } else if (challengeType === CHALLENGE_TYPE.DESIGN) { forumURL = api.config.tcConfig.studioForumsUrlPrefix + activeForumCategoryId; - submitURL = process.env.TC_STUDIO_SERVER_NAME + '/?module=ViewContestDetails&ct=' + challengeId; + //submitURL = process.env.TC_STUDIO_SERVER_NAME + '/?module=ViewContestDetails&ct=' + challengeId; + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/?type=develop'; } if (componentInfo.review_type && componentInfo.review_type == 'PEER') From 0757826d574df8674a733287b35322918cb016ef Mon Sep 17 00:00:00 2001 From: ajefts Date: Wed, 3 Aug 2016 15:48:35 -0400 Subject: [PATCH 06/11] fix typo in the registration email fix --- actions/challengeRegistration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index e9ac4260d..e4922a901 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -401,11 +401,11 @@ var sendNotificationEmail = function (api, componentInfo, userId, activeForumCat if (challengeType === CHALLENGE_TYPE.DEVELOP) { forumURL = api.config.tcConfig.developForumsUrlPrefix + activeForumCategoryId; reviewURL = process.env.TC_SOFTWARE_SERVER_NAME + '/review/actions/ViewProjectDetails?pid=' + challengeId; - submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenges/' + challengeId + '/submit/file/'; + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/?type=develop'; } else if (challengeType === CHALLENGE_TYPE.DESIGN) { forumURL = api.config.tcConfig.studioForumsUrlPrefix + activeForumCategoryId; //submitURL = process.env.TC_STUDIO_SERVER_NAME + '/?module=ViewContestDetails&ct=' + challengeId; - submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/?type=develop'; + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenges/' + challengeId + '/submit/file/'; } if (componentInfo.review_type && componentInfo.review_type == 'PEER') From db8875ab533f0f1fe78e7f20a871e718276c1cbe Mon Sep 17 00:00:00 2001 From: ajefts Date: Wed, 3 Aug 2016 15:56:28 -0400 Subject: [PATCH 07/11] Corrected challenge name Challenge name was having extra content added to the end --- actions/challengeRegistration.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index e9ac4260d..f50077b66 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -378,7 +378,7 @@ var sendNotificationEmail = function (api, componentInfo, userId, activeForumCat } user = result[0]; - projectName = componentInfo.project_name + api.helper.getPhaseName(componentInfo.phase_id) + ' Contest'; + projectName = componentInfo.project_name; documentationDetails = ''; // we need to set up a new environment variable for the web server name specifici to each environment //submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/'; @@ -401,11 +401,11 @@ var sendNotificationEmail = function (api, componentInfo, userId, activeForumCat if (challengeType === CHALLENGE_TYPE.DEVELOP) { forumURL = api.config.tcConfig.developForumsUrlPrefix + activeForumCategoryId; reviewURL = process.env.TC_SOFTWARE_SERVER_NAME + '/review/actions/ViewProjectDetails?pid=' + challengeId; - submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenges/' + challengeId + '/submit/file/'; + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/?type=develop'; } else if (challengeType === CHALLENGE_TYPE.DESIGN) { forumURL = api.config.tcConfig.studioForumsUrlPrefix + activeForumCategoryId; //submitURL = process.env.TC_STUDIO_SERVER_NAME + '/?module=ViewContestDetails&ct=' + challengeId; - submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/?type=develop'; + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenges/' + challengeId + '/submit/file/'; } if (componentInfo.review_type && componentInfo.review_type == 'PEER') From 1e67169bf45d6c2e7fc8a8f0cf43acb4e07cf747 Mon Sep 17 00:00:00 2001 From: test Date: Tue, 27 Sep 2016 18:06:45 -0700 Subject: [PATCH 08/11] Admin App - TC API Reviewer Management API --- actions/admins.js | 271 +++ actions/copilots.js | 228 ++ actions/reviewers.js | 276 ++- apiary-admin.apib | 611 +++++ ...in App - TC API Reviewer Management API.md | 121 + errors/DuplicateResourceError.js | 30 + initializers/helper.js | 111 +- queries/clear_user_rating | 1 + queries/clear_user_rating.json | 5 + queries/get_admin_resource | 1 + queries/get_admin_resource.json | 5 + queries/get_admins | 7 + queries/get_admins.json | 5 + queries/get_copilot_profile_id_by_user_id | 1 + .../get_copilot_profile_id_by_user_id.json | 5 + queries/get_copilots | 3 + queries/get_copilots.json | 5 + queries/get_next_admin_resource_id | 1 + queries/get_next_admin_resource_id.json | 5 + queries/get_next_admin_user_group_id | 1 + queries/get_next_admin_user_group_id.json | 5 + queries/get_project_category_by_category_id | 1 + .../get_project_category_by_category_id.json | 5 + queries/get_reviewer | 1 + queries/get_reviewer.json | 5 + queries/get_reviewers | 4 + queries/get_reviewers.json | 5 + queries/get_user_id_by_handle | 1 + queries/get_user_id_by_handle.json | 5 + queries/insert_admin_group | 1 + queries/insert_admin_group.json | 5 + queries/insert_admin_role | 1 + queries/insert_admin_role.json | 5 + queries/insert_new_admin_resource | 2 + queries/insert_new_admin_resource.json | 5 + queries/insert_new_admin_resource_info | 2 + queries/insert_new_admin_resource_info.json | 5 + queries/insert_new_copilot | 6 + queries/insert_new_copilot.json | 5 + queries/insert_reviewer | 1 + queries/insert_reviewer.json | 5 + queries/remove_admin_group | 1 + queries/remove_admin_group.json | 5 + queries/remove_admin_resource | 1 + queries/remove_admin_resource.json | 5 + queries/remove_admin_resource_info | 1 + queries/remove_admin_resource_info.json | 5 + queries/remove_admin_role | 1 + queries/remove_admin_role.json | 5 + queries/remove_copilot | 1 + queries/remove_copilot.json | 5 + queries/remove_reviewer | 1 + queries/remove_reviewer.json | 5 + routes.js | 37 +- test/docker/docker-compose.yml | 6 + test/postman/Reviewer_Management_API.json | 2041 +++++++++++++++++ .../Reviewer_Management_API_environment.json | 34 + test/scripts/bridge.js | 134 ++ test/sqls/admins/tcs_catalog__clean | 4 + .../sqls/admins/tcs_catalog__insert_test_data | 15 + test/sqls/copilots/tcs_catalog__clean | 1 + .../copilots/tcs_catalog__insert_test_data | 6 + test/sqls/reviewers/tcs_catalog__clean | 1 + .../reviewers/tcs_catalog__insert_test_data | 1 + test/test.admins.js | 143 ++ test/test.copilots.js | 149 ++ test/test.createAdmin.js | 185 ++ test/test.createCopilot.js | 207 ++ test/test.createReviewer.js | 284 +++ test/test.removeAdmin.js | 171 ++ test/test.removeCopilot.js | 183 ++ test/test.removeReviewer.js | 208 ++ test/test.reviewers.js | 149 ++ .../admins/expect_create_admin.json | 4 + .../expect_create_admin_with_empty_body.json | 3 + ...reate_admin_with_exist_admin_resource.json | 4 + test/test_files/admins/expect_get_admins.json | 11 + .../admins/expect_remove_admin.json | 4 + .../expect_remove_admin_with_empty_body.json | 3 + .../copilots/expect_create_copilot.json | 4 + ...expect_create_copilot_with_empty_body.json | 3 + ...opilot_with_missing_isSoftwareCopilot.json | 3 + ..._copilot_with_missing_isStudioCopilot.json | 3 + .../copilots/expect_get_copilots.json | 10 + .../copilots/expect_remove_copilot.json | 4 + ...expect_remove_copilot_with_empty_body.json | 3 + .../reviewers/expect_create_reviewer.json | 4 + ...xpect_create_reviewer_with_empty_body.json | 3 + ...eate_reviewer_with_missing_categoryId.json | 3 + .../reviewers/expect_get_reviewers.json | 12 + .../reviewers/expect_remove_reviewer.json | 4 + ...xpect_remove_reviewer_with_empty_body.json | 3 + ...move_reviewer_with_missing_categoryId.json | 3 + 93 files changed, 5842 insertions(+), 12 deletions(-) create mode 100755 actions/admins.js create mode 100755 actions/copilots.js mode change 100644 => 100755 actions/reviewers.js create mode 100755 apiary-admin.apib create mode 100755 docs/Admin App - TC API Reviewer Management API.md create mode 100755 errors/DuplicateResourceError.js mode change 100644 => 100755 initializers/helper.js create mode 100755 queries/clear_user_rating create mode 100755 queries/clear_user_rating.json create mode 100755 queries/get_admin_resource create mode 100755 queries/get_admin_resource.json create mode 100755 queries/get_admins create mode 100755 queries/get_admins.json create mode 100755 queries/get_copilot_profile_id_by_user_id create mode 100755 queries/get_copilot_profile_id_by_user_id.json create mode 100755 queries/get_copilots create mode 100755 queries/get_copilots.json create mode 100755 queries/get_next_admin_resource_id create mode 100755 queries/get_next_admin_resource_id.json create mode 100755 queries/get_next_admin_user_group_id create mode 100755 queries/get_next_admin_user_group_id.json create mode 100755 queries/get_project_category_by_category_id create mode 100755 queries/get_project_category_by_category_id.json create mode 100755 queries/get_reviewer create mode 100755 queries/get_reviewer.json create mode 100755 queries/get_reviewers create mode 100755 queries/get_reviewers.json create mode 100755 queries/get_user_id_by_handle create mode 100755 queries/get_user_id_by_handle.json create mode 100755 queries/insert_admin_group create mode 100755 queries/insert_admin_group.json create mode 100755 queries/insert_admin_role create mode 100755 queries/insert_admin_role.json create mode 100755 queries/insert_new_admin_resource create mode 100755 queries/insert_new_admin_resource.json create mode 100755 queries/insert_new_admin_resource_info create mode 100755 queries/insert_new_admin_resource_info.json create mode 100755 queries/insert_new_copilot create mode 100755 queries/insert_new_copilot.json create mode 100755 queries/insert_reviewer create mode 100755 queries/insert_reviewer.json create mode 100755 queries/remove_admin_group create mode 100755 queries/remove_admin_group.json create mode 100755 queries/remove_admin_resource create mode 100755 queries/remove_admin_resource.json create mode 100755 queries/remove_admin_resource_info create mode 100755 queries/remove_admin_resource_info.json create mode 100755 queries/remove_admin_role create mode 100755 queries/remove_admin_role.json create mode 100755 queries/remove_copilot create mode 100755 queries/remove_copilot.json create mode 100755 queries/remove_reviewer create mode 100755 queries/remove_reviewer.json create mode 100755 test/docker/docker-compose.yml create mode 100755 test/postman/Reviewer_Management_API.json create mode 100755 test/postman/Reviewer_Management_API_environment.json create mode 100755 test/scripts/bridge.js create mode 100755 test/sqls/admins/tcs_catalog__clean create mode 100755 test/sqls/admins/tcs_catalog__insert_test_data create mode 100755 test/sqls/copilots/tcs_catalog__clean create mode 100755 test/sqls/copilots/tcs_catalog__insert_test_data create mode 100755 test/sqls/reviewers/tcs_catalog__clean create mode 100755 test/sqls/reviewers/tcs_catalog__insert_test_data create mode 100755 test/test.admins.js create mode 100755 test/test.copilots.js create mode 100755 test/test.createAdmin.js create mode 100755 test/test.createCopilot.js create mode 100755 test/test.createReviewer.js create mode 100755 test/test.removeAdmin.js create mode 100755 test/test.removeCopilot.js create mode 100755 test/test.removeReviewer.js create mode 100755 test/test.reviewers.js create mode 100755 test/test_files/admins/expect_create_admin.json create mode 100755 test/test_files/admins/expect_create_admin_with_empty_body.json create mode 100755 test/test_files/admins/expect_create_admin_with_exist_admin_resource.json create mode 100755 test/test_files/admins/expect_get_admins.json create mode 100755 test/test_files/admins/expect_remove_admin.json create mode 100755 test/test_files/admins/expect_remove_admin_with_empty_body.json create mode 100755 test/test_files/copilots/expect_create_copilot.json create mode 100755 test/test_files/copilots/expect_create_copilot_with_empty_body.json create mode 100755 test/test_files/copilots/expect_create_copilot_with_missing_isSoftwareCopilot.json create mode 100755 test/test_files/copilots/expect_create_copilot_with_missing_isStudioCopilot.json create mode 100755 test/test_files/copilots/expect_get_copilots.json create mode 100755 test/test_files/copilots/expect_remove_copilot.json create mode 100755 test/test_files/copilots/expect_remove_copilot_with_empty_body.json create mode 100755 test/test_files/reviewers/expect_create_reviewer.json create mode 100755 test/test_files/reviewers/expect_create_reviewer_with_empty_body.json create mode 100755 test/test_files/reviewers/expect_create_reviewer_with_missing_categoryId.json create mode 100755 test/test_files/reviewers/expect_get_reviewers.json create mode 100755 test/test_files/reviewers/expect_remove_reviewer.json create mode 100755 test/test_files/reviewers/expect_remove_reviewer_with_empty_body.json create mode 100755 test/test_files/reviewers/expect_remove_reviewer_with_missing_categoryId.json diff --git a/actions/admins.js b/actions/admins.js new file mode 100755 index 000000000..842ebeae9 --- /dev/null +++ b/actions/admins.js @@ -0,0 +1,271 @@ +/*jslint nomen: true */ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +var _ = require('underscore'); +var async = require('async'); +var DuplicateResourceError = require('../errors/DuplicateResourceError'); + +/** + * This is the function that will actually get all admins. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var getAdmins = function (api, connection, dbConnectionMap, next) { + var helper = api.helper; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + api.dataAccess.executeQuery("get_admins", {}, dbConnectionMap, cb); + }, function (result, cb) { + var ret = {}, i, entity, type, id; + for (i = 0; i < result.length; i = i + 1) { + type = result[i].type.trim(); + id = result[i].user_id; + if (!ret[id]) { + ret[id] = { + id: result[i].user_id, + name: result[i].handle, + adminGroup: false, + adminRole: false, + managerResource: false + }; + } + entity = ret[id]; + if (type === 'Admin Group') { + entity.adminGroup = true; + } else if (type === 'Admin Role') { + entity.adminRole = true; + } else if (type === 'Manager Resource') { + entity.managerResource = true; + } + } + cb(null, { + allAdmins: _.values(ret) + }); + }], function (err, result) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); +}; + +/** + * The API for getting all admins + */ +exports.admins = { + name: "admins", + description: "retrieve all TopCoder admins", + inputs: { + required: [], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'read', // this action is read-only + databases: ['tcs_catalog'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute admins#run", 'debug'); + getAdmins(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually create admin. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var createAdmin = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, username = connection.params.username, userId, operatorId, parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + operatorId = connection.caller.userId; + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (id, cb) { + userId = id; + async.auto({ + nextUserGroupId: function (ca) { + api.dataAccess.executeQuery("get_next_admin_user_group_id", {}, dbConnectionMap, ca); + }, + nextResourceId: function (ca) { + api.dataAccess.executeQuery("get_next_admin_resource_id", {}, dbConnectionMap, ca); + } + }, cb); + }, function (results, cb) { + parameters = { + userId: userId, + userGroupId: results.nextUserGroupId[0].next_id, + operatorId: operatorId, + resourceId: results.nextResourceId[0].next_id + }; + api.dataAccess.executeQuery("insert_admin_group", parameters, dbConnectionMap, function (err) { + if (helper.isDuplicateResourceError(err)) { + cb(new DuplicateResourceError("User " + username + " has already been added to Admin Group", err)); + } else { + cb(err); + } + }); + }, function (cb) { + api.dataAccess.executeQuery("clear_user_rating", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }, function (cb) { + api.dataAccess.executeQuery("get_admin_resource", { + userId: userId + }, dbConnectionMap, cb); + }, function (resourceIds, cb) { + if (!resourceIds || !resourceIds.length) { + api.dataAccess.executeQuery("insert_new_admin_resource", parameters, dbConnectionMap, function (err) { + if (err) { + return cb(err); + } + api.dataAccess.executeQuery("insert_new_admin_resource_info", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }); + } else { + cb(null); + } + }, function (cb) { + api.dataAccess.executeQuery("insert_admin_role", parameters, dbConnectionMap, function (err) { + if (helper.isDuplicateResourceError(err)) { + cb(new DuplicateResourceError("User " + username + " has already been assigned Admin role", err)); + } else { + cb(err); + } + }); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + result.message = username + " has been successfully added as TopCoder Admin"; + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for creating admin + */ +exports.createAdmin = { + name: "createAdmin", + description: "create TopCoder admin", + inputs: { + required: ['username'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute createAdmin#run", 'debug'); + createAdmin(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually remove admin. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var removeAdmin = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, username = connection.params.username, operatorId, parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + operatorId = connection.caller.userId; + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (userId, cb) { + parameters = { + userId: userId, + operatorId: operatorId + }; + api.dataAccess.executeQuery("remove_admin_group", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }, function (cb) { + api.dataAccess.executeQuery("remove_admin_resource_info", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }, function (cb) { + api.dataAccess.executeQuery("remove_admin_resource", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }, function (cb) { + api.dataAccess.executeQuery("remove_admin_role", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + result.message = "TopCoder Admin: " + username + " has been successfully removed"; + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for removing admin + */ +exports.removeAdmin = { + name: "removeAdmin", + description: "remove TopCoder admin", + inputs: { + required: ['username'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute removeAdmin#run", 'debug'); + removeAdmin(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; diff --git a/actions/copilots.js b/actions/copilots.js new file mode 100755 index 000000000..a0fa9a4cf --- /dev/null +++ b/actions/copilots.js @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; + +var async = require('async'); +var IllegalArgumentError = require('../errors/IllegalArgumentError'); +var DuplicateResourceError = require('../errors/DuplicateResourceError'); +var NotFoundError = require('../errors/NotFoundError'); + +/** + * This is the function that will actually get all copilots. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var getCopilots = function (api, connection, dbConnectionMap, next) { + var helper = api.helper; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + api.dataAccess.executeQuery("get_copilots", {}, dbConnectionMap, cb); + }, function (result, cb) { + var ret = [], i, entity; + for (i = 0; i < result.length; i = i + 1) { + entity = {}; + entity.id = result[i].user_id; + entity.name = result[i].handle; + entity.softwareCopilot = result[i].is_software_copilot; + entity.studioCopilot = result[i].is_studio_copilot; + ret.push(entity); + } + cb(null, { + allCopilots: ret + }); + }], function (err, result) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); +}; + +/** + * The API for getting all copilots + */ +exports.copilots = { + name: "copilots", + description: "retrieve all copilots", + inputs: { + required: [], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'read', // this action is read-only + databases: ['tcs_catalog'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute copilots#run", 'debug'); + getCopilots(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually create copilot. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var createCopilot = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, + username = connection.params.username, + isSoftwareCopilot = connection.params.isSoftwareCopilot, + isStudioCopilot = connection.params.isStudioCopilot, + userId, + operatorId, + parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.') + || helper.checkBoolean(isSoftwareCopilot, 'isSoftwareCopilot') + || helper.checkBoolean(isStudioCopilot, 'isStudioCopilot')); + }, function (cb) { + operatorId = connection.caller.userId; + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (id, cb) { + userId = id; + if (!isStudioCopilot && !isSoftwareCopilot) { + return cb(new IllegalArgumentError("Studio Copilot and Software Copilot Checkbox should have at least one checked")); + } + helper.getCopilotProfileIdByUserId(userId, dbConnectionMap, cb); + }, function (copilotProfileId, cb) { + if (copilotProfileId > 0) { + return cb(new DuplicateResourceError("The user " + username + " is already added as copilot")); + } + parameters = { + userId: userId, + isSoftwareCopilot: isSoftwareCopilot ? 't' : 'f', + isStudioCopilot: isStudioCopilot ? 't' : 'f', + operatorId: operatorId + }; + api.dataAccess.executeQuery("insert_new_copilot", parameters, dbConnectionMap, cb); + }, function (effectedRows, cb) { + if (effectedRows === 1) { + result.message = "Copilot " + username + " has been successfully added"; + } + cb(null); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for creating Copilot + */ +exports.createCopilot = { + name: "createCopilot", + description: "create copilot", + inputs: { + required: ['username', 'isSoftwareCopilot', 'isStudioCopilot'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute createCopilot#run", 'debug'); + createCopilot(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually remove copilot. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var removeCopilot = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, username = connection.params.username, parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (userId, cb) { + parameters = { + userId: userId + }; + helper.getCopilotProfileIdByUserId(userId, dbConnectionMap, cb); + }, function (copilotProfileId, cb) { + if (copilotProfileId <= 0) { + return cb(new NotFoundError(username + " is not in the copilot pool")); + } + api.dataAccess.executeQuery("remove_copilot", parameters, dbConnectionMap, cb); + }, function (effectedRows, cb) { + if (effectedRows === 1) { + result.message = "Copilot " + username + " has been successfully removed"; + } + cb(null); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for removing copilot + */ +exports.removeCopilot = { + name: "removeCopilot", + description: "remove copilot", + inputs: { + required: ['username'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute removeCopilot#run", 'debug'); + removeCopilot(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; diff --git a/actions/reviewers.js b/actions/reviewers.js old mode 100644 new mode 100755 index a68421aee..510ab4ea0 --- a/actions/reviewers.js +++ b/actions/reviewers.js @@ -1,10 +1,35 @@ +/*jslint nomen: true */ /* - * Copyright (C) 2013 TopCoder Inc., All Rights Reserved. + * Copyright (C) 2013-2016 TopCoder Inc., All Rights Reserved. * - * @version 1.0 - * @author Sky_ + * @version 1.1 + * @author Sky_,TCSCODER + * Changes in 1.1: + * - add routes for Reviewer Management API + * - Add Reviewer + * - Remove Reviewer + * - Get All Reviewers */ "use strict"; +var _ = require('underscore'); +var async = require('async'); +var IllegalArgumentError = require('../errors/IllegalArgumentError'); +var NotFoundError = require('../errors/NotFoundError'); +var DuplicateResourceError = require('../errors/DuplicateResourceError'); + +/** + * The project type id for studio (design). + */ +var STUDIO_PROJECT_TYPE_ID = 3; +/** + * The category id of CODE. + */ +var CODE_CATEGORY_ID = 39; + +/** + * The category id of First2Finish. + */ +var F2F_CATEGORY_ID = 38; /** * Sample result from specification for Challenge Reviewers Collection @@ -12,8 +37,8 @@ var sampleReviewers; /** -* The API for getting challenge reviewers collection -*/ + * The API for getting challenge reviewers collection + */ exports.action = { name: "getChallengeReviewers", description: "getChallengeReviewers", @@ -60,4 +85,245 @@ sampleReviewers = { "photo": "4.gif" } ] +}; + +/** + * This is the function that will actually get all reviewers. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var getReviewers = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, categoryId = Number(connection.params.categoryId); + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.') + || helper.checkIdParameter(categoryId, 'categoryId')); + }, function (cb) { + api.dataAccess.executeQuery("get_reviewers", {categoryId: categoryId}, dbConnectionMap, cb); + }, function (result, cb) { + var ret = [], i, entity; + for (i = 0; i < result.length; i = i + 1) { + entity = {}; + entity.id = result[i].user_id; + entity.name = result[i].handle; + entity.projectCategoryId = result[i].project_category_id; + entity.projectCategoryName = result[i].project_category_name; + entity.immune = Number(result[i].immune_ind) === 1; + ret.push(entity); + } + cb(null, { + categoryId: categoryId, + reviewers: ret + }); + }], function (err, result) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); +}; + +/** + * The API for getting all reviewers of review board of a specific challenge category + */ +exports.reviewers = { + name: "reviewers", + description: "retrieve the reviewers of review board of a specific challenge category", + inputs: { + required: ['categoryId'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'read', // this action is read-only + databases: ['tcs_catalog'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute reviewers#run", 'debug'); + getReviewers(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually create reviewer. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var createReviewer = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, + username = connection.params.username, + categoryId = Number(connection.params.categoryId), + immune = connection.params.immune, + userId, + operatorId, + parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.') + || helper.checkIdParameter(categoryId, 'categoryId') || + (_.isUndefined(immune) ? null : helper.checkBoolean(immune, 'immune'))); + }, function (cb) { + operatorId = connection.caller.userId; + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (id, cb) { + userId = id; + helper.getProjectCategoryByCategoryId(categoryId, dbConnectionMap, cb); + }, function (projectCategory, cb) { + if (!projectCategory) { + return cb(new IllegalArgumentError("Category Id " + categoryId + " is not a valid category ID")); + } + var isImmunity = projectCategory.typeId === STUDIO_PROJECT_TYPE_ID || categoryId === CODE_CATEGORY_ID || categoryId === F2F_CATEGORY_ID; + // will use immune from user input if exist + if (!_.isUndefined(immune)) { + isImmunity = immune; + } + // will use 1 or 0 finally + isImmunity = isImmunity ? 1 : 0; + parameters = { + userId: userId, + operatorId: operatorId, + categoryId: categoryId, + isImmunity: isImmunity + }; + api.dataAccess.executeQuery("insert_reviewer", parameters, dbConnectionMap, function (err, effectedRows) { + if (helper.isDuplicateResourceError(err)) { + cb(new DuplicateResourceError("User " + username + " is in the specific review board", err)); + } else { + if (!err && effectedRows === 1) { + result.message = username + " has been successfully added into " + projectCategory.name + " Review Board"; + } + cb(err); + } + }); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for creating reviewer + */ +exports.createReviewer = { + name: "createReviewer", + description: "add reviewer", + inputs: { + required: ['username', 'categoryId'], + optional: ['immune'] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute createReviewer#run", 'debug'); + createReviewer(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually remove reviewer. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var removeReviewer = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, + username = connection.params.username, + categoryId = Number(connection.params.categoryId), + parameters, + userId, + projectCategory, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.') + || helper.checkIdParameter(categoryId, 'categoryId')); + }, function (cb) { + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (id, cb) { + userId = id; + helper.getProjectCategoryByCategoryId(categoryId, dbConnectionMap, cb); + }, function (projectCategoryResult, cb) { + projectCategory = projectCategoryResult; + if (!projectCategory) { + return cb(new IllegalArgumentError("Category Id " + categoryId + " is not a valid category ID")); + } + parameters = { + userId: userId, + categoryId: categoryId + }; + api.dataAccess.executeQuery("get_reviewer", parameters, dbConnectionMap, cb); + }, function (userIds, cb) { + if (!userIds || !userIds.length) { + return cb(new NotFoundError("There is no reviewer with the username:" + username + " in category: " + projectCategory.name)); + } + api.dataAccess.executeQuery("remove_reviewer", parameters, dbConnectionMap, cb); + }, function (effectedRows, cb) { + if (effectedRows >= 1) { + result.message = username + " has been successfully removed from " + projectCategory.name + " Review Board"; + } + cb(null); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for removing reviewer + */ +exports.removeReviewer = { + name: "removeReviewer", + description: "remove reviewer", + inputs: { + required: ['username', 'categoryId'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute removeReviewer#run", 'debug'); + removeReviewer(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } }; \ No newline at end of file diff --git a/apiary-admin.apib b/apiary-admin.apib new file mode 100755 index 000000000..0bdb7b4f3 --- /dev/null +++ b/apiary-admin.apib @@ -0,0 +1,611 @@ +FORMAT: 1A +HOST: http://api.topcoder.com/v2 + +# Reviewer Management API + +We extend the tc-api to provide new endpoints to handle admin / copilot / reviewer related +tasks. + +## Admins Collection [/admin/admins] + +### List All admins [GET] ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "allAdmins": [ + { + "id": 132456, + "name": "heffan", + "adminGroup": true, + "adminRole": true, + "managerResource": true + }] + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Create New Admin [POST] ++ Attributes + + username: `dok_tester` (required, string) ... The username + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "dok_tester has been successfully added as TopCoder Admin" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 409 (application/json) + + { + "name":"Duplicate Resource", + "value":"409", + "description":"The request is understood, but has duplicate resource." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Remove Admin [DELETE] ++ Attributes + + username: `dok_tester` (required, string) ... The username + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "TopCoder Admin: dok_tester has been successfully removed" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +## Copilots Collection [/admin/copilots] + +### List All copilots [GET] ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "allCopilots": [ + { + "id": 20, + "name": "dok_tester", + "softwareCopilot": true, + "studioCopilot": true + } + ] + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Create New Copilot [POST] ++ Attributes + + username: `dok_tester` (required, string) ... The username + + isSoftwareCopilot: `true` (required, boolean) ... The isSoftwareCopilot flag + + isStudioCopilot: `true` (required, boolean) ... The isStudioCopilot flag + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "Copilot dok_tester has been successfully added" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 409 (application/json) + + { + "name":"Duplicate Resource", + "value":"409", + "description":"The request is understood, but has duplicate resource." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Remove Copilot [DELETE] ++ Attributes + + username: `dok_tester` (required, string) ... The username + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "Copilot dok_tester has been successfully removed" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +## Reviewers Collection [/admin/reviewers?categoryId={categoryId}] + +### List All reviewers [GET] ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Parameters + + categoryId (required, number, `7`) ... Project Category Id + ++ Response 200 (application/json) + + { + "categoryId": 7, + "reviewers": [ + { + "id": 20, + "name": "dok_tester", + "projectCategoryId": 7, + "projectCategoryName": "Architecture", + "immune": false + } + ] + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Create New Reviewer [POST] ++ Attributes + + username: `dok_tester` (required, string) ... The username + + categoryId: `14` (required, number) ... The project category id, you can use category id in query but recommend to use category in body. + + immune: `true` (optional, boolean) ... The immune_ind flag + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "dok_tester has been successfully added into Assembly Competition Review Board" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 409 (application/json) + + { + "name":"Duplicate Resource", + "value":"409", + "description":"The request is understood, but has duplicate resource." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Remove Reviewer [DELETE] ++ Attributes + + username: `dok_tester` (required, string) ... The username + + categoryId: `14` (required, number) ... The project category id, you can use category id in query but recommend to use category in body. + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "dok_tester has been successfully removed from Assembly Competition Review Board" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } diff --git a/docs/Admin App - TC API Reviewer Management API.md b/docs/Admin App - TC API Reviewer Management API.md new file mode 100755 index 000000000..58b40e995 --- /dev/null +++ b/docs/Admin App - TC API Reviewer Management API.md @@ -0,0 +1,121 @@ +# Admin App - TC API Reviewer Management API +In this challenge, we need to enhance / extend the tc-api to provide new endpoints to handle admin / copilot / reviewer related tasks. + +## Setup +- [nodejs 0.10.x](https://nodejs.org/) +- [Docker](https://docs.docker.com/engine/installation/) +- [docker-compose](https://docs.docker.com/compose/install/) +- java(required by https://github.com/appirio-tech/informix-wrapper) +It will actually use [node-java](https://github.com/joeferner/node-java), if you meet any issues please check there. + +informix docker service +``` +cd test/docker +docker-compose up +``` + +Follow exist wiki to setup application [wiki](https://github.com/appirio-tech/tc-api/wiki), please do not run `npm test` since old tests are broken. +TC_VM_IP could be `127.0.0.1` under linux or `192.168.99.100` under mac or windows using docker tool box. +``` +# follow wiki to start applications(all previous steps are required) +npm start +``` + +mock bridge service +``` +# you must prepare same environment variables as the tc-api https://github.com/appirio-tech/tc-api/wiki/Configure-Environment-Variables +# I assume you have install all dependencies +node test/scripts/bridge +``` +Or you can user real java version using **java8**. +Download tc-api-jdbc-bridge-dev.zip in https://apps.topcoder.com/forums//?module=Thread&threadID=891500&start=0 +unzip and run `mvn clean package` +update `src/main/resources/bridge.yml` as expected +authDomain: topcoder-dev.com +dbStore/dwStore should change to match configurations in `tc-api/deploy/development.bat` or `development.sh` +``` +TC_DB_NAME=informixoltp_tcp +TC_DB_HOST=$VM_IP +TC_DB_PORT=2021 +TC_DB_USER=informix +TC_DB_PASSWORD=1nf0rm1x + +TC_DW_NAME=informixoltp_tcp +TC_DW_HOST=$VM_IP +TC_DW_PORT=2021 +TC_DW_USER=informix +TC_DW_PASSWORD=1nf0rm1x +``` + +## lint +``` +# you may need to add sudo under linux or mac +npm install jslint -g +jslint routes.js +jslint actions/admins.js +jslint actions/copilots.js +jslint actions/reviewers.js +jslint errors/DuplicateResourceError.js + +# exist lint errors in old codes will not fix +jslint initializers/helper.js + +jslint test/scripts/bridge.js + +jslint test/test.admins.js +jslint test/test.createAdmin.js +jslint test/test.removeAdmin.js + +jslint test/test.copilots.js +jslint test/test.createCopilot.js +jslint test/test.removeCopilot.js + +jslint test/test.reviewers.js +jslint test/test.createReviewer.js +jslint test/test.removeReviewer.js +``` + +## Verify by postman +Import postman collection `test/postman/Reviewer_Management_API.json` and environment `test/postman/Reviewer_Management_API_environment.json`. +Make sure tc api is listening `8080` of localhost rightly or url in environment is right for `http://localhost:8080/api/v2`(mocha test will use this url too). +Make sure informix, bridge is also running. +You can verify requests in different folder. +If token is expired please run requests in `login` folder and Log in as admin or ordinary user and update `adminToken`, `userToken` in environment. + +## Verify by mocha +It will run similar requests as postman including failure and success cases. + +Please make sure informix, tc-api, bridge service is running, it is slow to run single test +and easy to occur max connection numbers issues so it is better to test files one by one and restart all applications if meets any error. +``` +# you must prepare same environment variables as the tc-api https://github.com/appirio-tech/tc-api/wiki/Configure-Environment-Variables +# you may need to add sudo under linux or mac +npm install mocha -g +mocha test/test.admins.js +mocha test/test.createAdmin.js +mocha test/test.removeAdmin.js + +mocha test/test.copilots.js +mocha test/test.createCopilot.js +mocha test/test.removeCopilot.js + +mocha test/test.reviewers.js +mocha test/test.createReviewer.js +mocha test/test.removeReviewer.js +``` + +## api doc +Register account in https://apiary.io and create new api and copy document `tc-api/apiary-admin.apib` and validate document, +Save and you can click Documentation tab on top to view api doc. + + +## Max connection number issue +The docker image of informix is limited to have 20 connections.It is very easy to occur **Open Timeout** +or **Timed out without obtaining all DB connections** or **Error: The server experienced an internal error** during test,postman requests. +You should close all services/applications include bridge service and stop and restart informix again with previous steps. +You may change `MAXPOOL` environment variable since it will be shared in tc api, bridge,test applications so use `20` is not proper actually. + +## Pass data to api +when add/remove reviewers you could use categoryId/username in query or body, but I recommend you to send data in body +since category id in query will be ignored if exists categoryId in body +and it could send number directly in body(provide postman requests for category id in query too). \ No newline at end of file diff --git a/errors/DuplicateResourceError.js b/errors/DuplicateResourceError.js new file mode 100755 index 000000000..909368cde --- /dev/null +++ b/errors/DuplicateResourceError.js @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016 TopCoder, Inc. All rights reserved. + */ +"use strict"; + +/** + * This file defines DuplicateResourceError + * + * @author TCSCODER + * @version 1.0 + */ + +/** + * Constructor of DuplicateResourceError + * @param {Object} message the error message + * @param {Object} cause the error cause + */ +var DuplicateResourceError = function (message, cause) { + //captureStackTrace + Error.call(this); + Error.captureStackTrace(this); + this.message = message || "DuplicateResource Error"; + this.cause = cause; +}; + +//use Error as prototype +require('util').inherits(DuplicateResourceError, Error); +DuplicateResourceError.prototype.name = 'DuplicateResource Error'; + +module.exports = DuplicateResourceError; diff --git a/initializers/helper.js b/initializers/helper.js old mode 100644 new mode 100755 index 2e53f1cc6..7170bd95d --- a/initializers/helper.js +++ b/initializers/helper.js @@ -1,12 +1,12 @@ /*jslint node: true, nomen: true, unparam: true, plusplus: true, bitwise: true */ /** - * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. + * Copyright (C) 2013 - 2016 TopCoder Inc., All Rights Reserved. */ /** * This module contains helper functions. * @author Sky_, Ghost_141, muzehyun, kurtrips, isv, LazyChild, hesibo, panoptimum, flytoj2ee, TCSASSEMBLER - * @version 1.42 + * @version 1.43 * changes in 1.1: * - add mapProperties * changes in 1.2: @@ -112,6 +112,12 @@ * - Update apiName2dbNameMap to add entries for srm schedule API. * Changes in 1.42: * - Add checkAdminOrWebArenaSuper to check if user has web arena super role. + * Changes in 1.43: + * - Add validateUserAndGetUserId to validate username and get user id. + * - Add isDuplicateResourceError to check whether error is duplicate resource error + * - Add getCopilotProfileIdByUserId to get copilot profile id by user id + * - Add getProjectCategoryByCategoryId to get project category with type,name by category id + * - Update apiCode, handleError to handle duplicate resource error */ "use strict"; @@ -136,6 +142,7 @@ var BadRequestError = require('../errors/BadRequestError'); var UnauthorizedError = require('../errors/UnauthorizedError'); var ForbiddenError = require('../errors/ForbiddenError'); var RequestTooLargeError = require('../errors/RequestTooLargeError'); +var DuplicateResourceError = require('../errors/DuplicateResourceError'); var helper = {}; var crypto = require("crypto"); var bigdecimal = require('bigdecimal'); @@ -1039,6 +1046,9 @@ helper.consts.ALLOWABLE_DATE_TYPE = [helper.consts.AFTER, helper.consts.AFTER_CU /** * Api codes + * changes in 1.43 + * -add code,value,description for duplicate resource error + * @since 1.43 */ helper.apiCodes = { OK: { @@ -1071,6 +1081,11 @@ helper.apiCodes = { value: 413, description: 'The request is understood, but is larger than the server is willing or able to process.' }, + duplicateResource: { + name: 'Duplicate Resource', + value: 409, + description: 'The request is understood, but has duplicate resource.' + }, notFound: { name: 'Not Found', value: 404, @@ -1089,6 +1104,9 @@ helper.apiCodes = { * @param {Object} api - The api object that is used to access the global infrastructure * @param {Object} connection - The connection object for the current request * @param {Object} err - The error to return + * changes in 1.43 + * -handle duplicate resource error + * @since 1.43 */ helper.handleError = function (api, connection, err) { api.log("Error occurred: " + err + " " + (err.stack || ''), "error"); @@ -1108,6 +1126,9 @@ helper.handleError = function (api, connection, err) { if (err instanceof RequestTooLargeError) { baseError = helper.apiCodes.requestTooLarge; } + if (err instanceof DuplicateResourceError) { + baseError = helper.apiCodes.duplicateResource; + } errdetail = _.clone(baseError); errdetail.details = err.message; connection.rawConnection.responseHttpCode = baseError.value; @@ -1980,6 +2001,92 @@ var getCoderIdFromActivationCode = function (activationCode) { helper.getCoderIdFromActivationCode = getCoderIdFromActivationCode; helper.generateActivationCode = generateActivationCode; +/** + * Validate the given user username and get user id. + * @param {String} username - the user username. + * @param {Object} dbConnectionMap - the database connection map + * @param {Function} callback - The callback function. + * @since 1.43 + */ +helper.validateUserAndGetUserId = function (username, dbConnectionMap, callback) { + async.waterfall([ + function (cb) { + cb(helper.checkStringPopulated(username, 'username')); + }, + function (cb) { + helper.api.dataAccess.executeQuery("get_user_id_by_handle", + { handle: username }, dbConnectionMap, cb); + }, function (result, cb) { + if (!result || !result.length) { + return cb(new NotFoundError("User with the username: " + username + " does not exist")); + } + cb(null, result[0].user_id); + } + ], callback); +}; + +/** + * Check whether given error is duplicate resource error. + * @param e the error object + * @returns true if contains duplicate in error message. + * @since 1.43 + */ +helper.isDuplicateResourceError = function (e) { + return e && e.message && e.message.indexOf('duplicate') !== -1; +}; + +/** + * Get copilot profile id by user id. + * @param {Number} userId - the user id. + * @param {Object} dbConnectionMap - the database connection map + * @param {Function} callback - The callback function. + * @since 1.43 + */ +helper.getCopilotProfileIdByUserId = function (userId, dbConnectionMap, callback) { + async.waterfall([ + function (cb) { + cb(helper.checkIdParameter(userId, 'userId')); + }, + function (cb) { + helper.api.dataAccess.executeQuery("get_copilot_profile_id_by_user_id", + { userId: userId }, dbConnectionMap, cb); + }, function (result, cb) { + cb(null, (!result || !result.length) ? 0 : result[0].copilot_profile_id); + } + ], callback); +}; + + +/** + * Gets project category with project type id,name by project category ID. + * @param {Number} categoryId - the project category id. + * @param {Object} dbConnectionMap - the database connection map + * @param {Function} callback - The callback function. + * @since 1.43 + */ +helper.getProjectCategoryByCategoryId = function (categoryId, dbConnectionMap, callback) { + async.waterfall([ + function (cb) { + cb(helper.checkIdParameter(categoryId, 'categoryId')); + }, + function (cb) { + helper.api.dataAccess.executeQuery("get_project_category_by_category_id", + {categoryId: categoryId}, dbConnectionMap, cb); + }, function (result, cb) { + var projectCategory = null; + if (result && result.length) { + projectCategory = { + id: result[0].project_category_id, + name: result[0].name, + typeId: Number(result[0].project_type_id) + }; + } + cb(null, projectCategory); + } + ], callback); +}; + + /** * Expose the "helper" utility. * diff --git a/queries/clear_user_rating b/queries/clear_user_rating new file mode 100755 index 000000000..6a8d7b175 --- /dev/null +++ b/queries/clear_user_rating @@ -0,0 +1 @@ +UPDATE informixoltp:rating SET rating = -1 WHERE coder_id = @userId@ \ No newline at end of file diff --git a/queries/clear_user_rating.json b/queries/clear_user_rating.json new file mode 100755 index 000000000..3f313986a --- /dev/null +++ b/queries/clear_user_rating.json @@ -0,0 +1,5 @@ +{ + "name": "clear_user_rating", + "db": "tcs_catalog", + "sqlfile": "clear_user_rating" +} \ No newline at end of file diff --git a/queries/get_admin_resource b/queries/get_admin_resource new file mode 100755 index 000000000..23e22d01a --- /dev/null +++ b/queries/get_admin_resource @@ -0,0 +1 @@ +select resource_id from resource where project_id IS NULL and resource_role_id = 13 and user_id = @userId@ \ No newline at end of file diff --git a/queries/get_admin_resource.json b/queries/get_admin_resource.json new file mode 100755 index 000000000..04bbb3a4d --- /dev/null +++ b/queries/get_admin_resource.json @@ -0,0 +1,5 @@ +{ + "name": "get_admin_resource", + "db": "tcs_catalog", + "sqlfile": "get_admin_resource" +} \ No newline at end of file diff --git a/queries/get_admins b/queries/get_admins new file mode 100755 index 000000000..f654a5770 --- /dev/null +++ b/queries/get_admins @@ -0,0 +1,7 @@ +select t.user_id, t.type, u.handle +FROM (select unique user_id, 'Manager Resource' as type from resource where project_id IS NULL and resource_role_id = 13 +UNION +select unique login_id as user_id, 'Admin Group' as type from user_group_xref where group_id = 2000115 +UNION +select unique login_id as user_id, 'Admin Role' as type from user_role_xref where role_id = 2087) as t, user u +WHERE t.user_id = u.user_id diff --git a/queries/get_admins.json b/queries/get_admins.json new file mode 100755 index 000000000..82ac59fb0 --- /dev/null +++ b/queries/get_admins.json @@ -0,0 +1,5 @@ +{ + "name" : "get_admins", + "db" : "tcs_catalog", + "sqlfile" : "get_admins" +} \ No newline at end of file diff --git a/queries/get_copilot_profile_id_by_user_id b/queries/get_copilot_profile_id_by_user_id new file mode 100755 index 000000000..6c98ddce9 --- /dev/null +++ b/queries/get_copilot_profile_id_by_user_id @@ -0,0 +1 @@ +SELECT copilot_profile_id FROM copilot_profile WHERE user_id = @userId@ \ No newline at end of file diff --git a/queries/get_copilot_profile_id_by_user_id.json b/queries/get_copilot_profile_id_by_user_id.json new file mode 100755 index 000000000..0f5e02acc --- /dev/null +++ b/queries/get_copilot_profile_id_by_user_id.json @@ -0,0 +1,5 @@ +{ + "name" : "get_copilot_profile_id_by_user_id", + "db" : "tcs_catalog", + "sqlfile" : "get_copilot_profile_id_by_user_id" +} \ No newline at end of file diff --git a/queries/get_copilots b/queries/get_copilots new file mode 100755 index 000000000..b7076889b --- /dev/null +++ b/queries/get_copilots @@ -0,0 +1,3 @@ +select cp.user_id, handle, is_software_copilot, is_studio_copilot +from copilot_profile cp, user u +where cp.user_id = u.user_id and cp.copilot_profile_status_id = 1 \ No newline at end of file diff --git a/queries/get_copilots.json b/queries/get_copilots.json new file mode 100755 index 000000000..a43d82492 --- /dev/null +++ b/queries/get_copilots.json @@ -0,0 +1,5 @@ +{ + "name" : "get_copilots", + "db" : "tcs_catalog", + "sqlfile" : "get_copilots" +} \ No newline at end of file diff --git a/queries/get_next_admin_resource_id b/queries/get_next_admin_resource_id new file mode 100755 index 000000000..7f59c9ebc --- /dev/null +++ b/queries/get_next_admin_resource_id @@ -0,0 +1 @@ +SELECT (NVL(max(resource_id),0) + 1) as next_id FROM resource WHERE resource_id <5000 \ No newline at end of file diff --git a/queries/get_next_admin_resource_id.json b/queries/get_next_admin_resource_id.json new file mode 100755 index 000000000..a19d8082b --- /dev/null +++ b/queries/get_next_admin_resource_id.json @@ -0,0 +1,5 @@ +{ + "name": "get_next_admin_resource_id", + "db": "tcs_catalog", + "sqlfile": "get_next_admin_resource_id" +} \ No newline at end of file diff --git a/queries/get_next_admin_user_group_id b/queries/get_next_admin_user_group_id new file mode 100755 index 000000000..0b1be8031 --- /dev/null +++ b/queries/get_next_admin_user_group_id @@ -0,0 +1 @@ +SELECT (NVL(MIN(user_group_id), 0) - 1) as next_id FROM user_group_xref \ No newline at end of file diff --git a/queries/get_next_admin_user_group_id.json b/queries/get_next_admin_user_group_id.json new file mode 100755 index 000000000..58b6e863c --- /dev/null +++ b/queries/get_next_admin_user_group_id.json @@ -0,0 +1,5 @@ +{ + "name": "get_next_admin_user_group_id", + "db": "tcs_catalog", + "sqlfile": "get_next_admin_user_group_id" +} \ No newline at end of file diff --git a/queries/get_project_category_by_category_id b/queries/get_project_category_by_category_id new file mode 100755 index 000000000..2a5585ef5 --- /dev/null +++ b/queries/get_project_category_by_category_id @@ -0,0 +1 @@ +select project_category_id,project_type_id,name from project_category_lu where display = 't' and project_category_id =@categoryId@ diff --git a/queries/get_project_category_by_category_id.json b/queries/get_project_category_by_category_id.json new file mode 100755 index 000000000..a65bcec28 --- /dev/null +++ b/queries/get_project_category_by_category_id.json @@ -0,0 +1,5 @@ +{ + "name" : "get_project_category_by_category_id", + "db" : "tcs_catalog", + "sqlfile" : "get_project_category_by_category_id" +} diff --git a/queries/get_reviewer b/queries/get_reviewer new file mode 100755 index 000000000..d8b55f3b9 --- /dev/null +++ b/queries/get_reviewer @@ -0,0 +1 @@ +SELECT user_id FROM rboard_user WHERE user_id = @userId@ AND project_type_id = @categoryId@ \ No newline at end of file diff --git a/queries/get_reviewer.json b/queries/get_reviewer.json new file mode 100755 index 000000000..dc4d36389 --- /dev/null +++ b/queries/get_reviewer.json @@ -0,0 +1,5 @@ +{ + "name" : "get_reviewer", + "db" : "tcs_catalog", + "sqlfile" : "get_reviewer" +} diff --git a/queries/get_reviewers b/queries/get_reviewers new file mode 100755 index 000000000..d44830348 --- /dev/null +++ b/queries/get_reviewers @@ -0,0 +1,4 @@ +select u.user_id, pcl.project_category_id, u.handle, pcl.name as project_category_name, immune_ind +from rboard_user ru, user u, project_category_lu pcl +where u.user_id = ru.user_id AND ru.project_type_id = pcl.project_category_id AND pcl.project_category_id = @categoryId@ +group by 1,2,3,4,5 \ No newline at end of file diff --git a/queries/get_reviewers.json b/queries/get_reviewers.json new file mode 100755 index 000000000..8b34c5a16 --- /dev/null +++ b/queries/get_reviewers.json @@ -0,0 +1,5 @@ +{ + "name" : "get_reviewers", + "db" : "tcs_catalog", + "sqlfile" : "get_reviewers" +} diff --git a/queries/get_user_id_by_handle b/queries/get_user_id_by_handle new file mode 100755 index 000000000..bf5a319c2 --- /dev/null +++ b/queries/get_user_id_by_handle @@ -0,0 +1 @@ +select user_id from user where handle = '@handle@' \ No newline at end of file diff --git a/queries/get_user_id_by_handle.json b/queries/get_user_id_by_handle.json new file mode 100755 index 000000000..f16209326 --- /dev/null +++ b/queries/get_user_id_by_handle.json @@ -0,0 +1,5 @@ +{ + "name": "get_user_id_by_handle", + "db": "common_oltp", + "sqlfile": "get_user_id_by_handle" +} \ No newline at end of file diff --git a/queries/insert_admin_group b/queries/insert_admin_group new file mode 100755 index 000000000..94f91b568 --- /dev/null +++ b/queries/insert_admin_group @@ -0,0 +1 @@ +INSERT INTO user_group_xref VALUES(@userGroupId@, @userId@, 2000115, @operatorId@, 1, current) \ No newline at end of file diff --git a/queries/insert_admin_group.json b/queries/insert_admin_group.json new file mode 100755 index 000000000..fff9f859d --- /dev/null +++ b/queries/insert_admin_group.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_admin_group", + "db" : "tcs_catalog", + "sqlfile" : "insert_admin_group" +} \ No newline at end of file diff --git a/queries/insert_admin_role b/queries/insert_admin_role new file mode 100755 index 000000000..52b9a92d0 --- /dev/null +++ b/queries/insert_admin_role @@ -0,0 +1 @@ +INSERT INTO user_role_xref VALUES ((SELECT NVL(MIN(user_role_id), 0) - 1 FROM user_role_xref), @userId@, 2087, @operatorId@, 1) \ No newline at end of file diff --git a/queries/insert_admin_role.json b/queries/insert_admin_role.json new file mode 100755 index 000000000..24aa65f84 --- /dev/null +++ b/queries/insert_admin_role.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_admin_role", + "db" : "tcs_catalog", + "sqlfile" : "insert_admin_role" +} \ No newline at end of file diff --git a/queries/insert_new_admin_resource b/queries/insert_new_admin_resource new file mode 100755 index 000000000..2d76f63a1 --- /dev/null +++ b/queries/insert_new_admin_resource @@ -0,0 +1,2 @@ +INSERT INTO resource (resource_id, resource_role_id, user_id, create_user, create_date, modify_user, modify_date) +VALUES(@resourceId@, 13, @userId@, @operatorId@, current, @operatorId@, current) \ No newline at end of file diff --git a/queries/insert_new_admin_resource.json b/queries/insert_new_admin_resource.json new file mode 100755 index 000000000..bd2f0b879 --- /dev/null +++ b/queries/insert_new_admin_resource.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_new_admin_resource", + "db" : "tcs_catalog", + "sqlfile" : "insert_new_admin_resource" +} diff --git a/queries/insert_new_admin_resource_info b/queries/insert_new_admin_resource_info new file mode 100755 index 000000000..c9c6a1f6f --- /dev/null +++ b/queries/insert_new_admin_resource_info @@ -0,0 +1,2 @@ +INSERT INTO resource_info (resource_id, resource_info_type_id, value, create_user, create_date, modify_user, modify_date) +VALUES(@resourceId@, 1 , @userId@, @operatorId@, current, @operatorId@, current) \ No newline at end of file diff --git a/queries/insert_new_admin_resource_info.json b/queries/insert_new_admin_resource_info.json new file mode 100755 index 000000000..ae950aa10 --- /dev/null +++ b/queries/insert_new_admin_resource_info.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_new_admin_resource_info", + "db" : "tcs_catalog", + "sqlfile" : "insert_new_admin_resource_info" +} diff --git a/queries/insert_new_copilot b/queries/insert_new_copilot new file mode 100755 index 000000000..120f1919a --- /dev/null +++ b/queries/insert_new_copilot @@ -0,0 +1,6 @@ +INSERT INTO copilot_profile (copilot_profile_id,user_id,copilot_profile_status_id,suspension_count, +reliability,activation_date,show_copilot_earnings,create_user, create_date, +update_user, update_date, is_software_copilot, is_studio_copilot) +VALUES ((select NVL(max(copilot_profile_id), 0) + 1 from copilot_profile), +@userId@,1,0,100.00, current,'t', @operatorId@, +current, @operatorId@, current, '@isSoftwareCopilot@', '@isStudioCopilot@') \ No newline at end of file diff --git a/queries/insert_new_copilot.json b/queries/insert_new_copilot.json new file mode 100755 index 000000000..c66d0a50b --- /dev/null +++ b/queries/insert_new_copilot.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_new_copilot", + "db" : "tcs_catalog", + "sqlfile" : "insert_new_copilot" +} \ No newline at end of file diff --git a/queries/insert_reviewer b/queries/insert_reviewer new file mode 100755 index 000000000..289153270 --- /dev/null +++ b/queries/insert_reviewer @@ -0,0 +1 @@ +INSERT INTO rboard_user VALUES (@userId@, @categoryId@, 4, 100, @isImmunity@) \ No newline at end of file diff --git a/queries/insert_reviewer.json b/queries/insert_reviewer.json new file mode 100755 index 000000000..bd4300485 --- /dev/null +++ b/queries/insert_reviewer.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_reviewer", + "db" : "tcs_catalog", + "sqlfile" : "insert_reviewer" +} diff --git a/queries/remove_admin_group b/queries/remove_admin_group new file mode 100755 index 000000000..bcee9ce54 --- /dev/null +++ b/queries/remove_admin_group @@ -0,0 +1 @@ +DELETE FROM user_group_xref WHERE login_id = @userId@ AND group_id = 2000115 \ No newline at end of file diff --git a/queries/remove_admin_group.json b/queries/remove_admin_group.json new file mode 100755 index 000000000..10ff55e15 --- /dev/null +++ b/queries/remove_admin_group.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_admin_group", + "db" : "tcs_catalog", + "sqlfile" : "remove_admin_group" +} \ No newline at end of file diff --git a/queries/remove_admin_resource b/queries/remove_admin_resource new file mode 100755 index 000000000..90ba7760f --- /dev/null +++ b/queries/remove_admin_resource @@ -0,0 +1 @@ +DELETE FROM resource WHERE resource_role_id = 13 AND project_id IS NULL AND user_id = @userId@ \ No newline at end of file diff --git a/queries/remove_admin_resource.json b/queries/remove_admin_resource.json new file mode 100755 index 000000000..b309c6aa4 --- /dev/null +++ b/queries/remove_admin_resource.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_admin_resource", + "db" : "tcs_catalog", + "sqlfile" : "remove_admin_resource" +} \ No newline at end of file diff --git a/queries/remove_admin_resource_info b/queries/remove_admin_resource_info new file mode 100755 index 000000000..9bbcd7d59 --- /dev/null +++ b/queries/remove_admin_resource_info @@ -0,0 +1 @@ +DELETE FROM resource_info WHERE resource_id IN (SELECT resource_id FROM resource WHERE resource_role_id = 13 AND project_id IS NULL AND user_id = @userId@) \ No newline at end of file diff --git a/queries/remove_admin_resource_info.json b/queries/remove_admin_resource_info.json new file mode 100755 index 000000000..a8d90d387 --- /dev/null +++ b/queries/remove_admin_resource_info.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_admin_resource_info", + "db" : "tcs_catalog", + "sqlfile" : "remove_admin_resource_info" +} \ No newline at end of file diff --git a/queries/remove_admin_role b/queries/remove_admin_role new file mode 100755 index 000000000..8b8e70b3b --- /dev/null +++ b/queries/remove_admin_role @@ -0,0 +1 @@ +DELETE FROM user_role_xref WHERE role_id=2087 AND login_id = @userId@ \ No newline at end of file diff --git a/queries/remove_admin_role.json b/queries/remove_admin_role.json new file mode 100755 index 000000000..13be813dd --- /dev/null +++ b/queries/remove_admin_role.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_admin_role", + "db" : "tcs_catalog", + "sqlfile" : "remove_admin_role" +} \ No newline at end of file diff --git a/queries/remove_copilot b/queries/remove_copilot new file mode 100755 index 000000000..296479222 --- /dev/null +++ b/queries/remove_copilot @@ -0,0 +1 @@ +DELETE FROM copilot_profile WHERE user_id = @userId@ \ No newline at end of file diff --git a/queries/remove_copilot.json b/queries/remove_copilot.json new file mode 100755 index 000000000..685beb670 --- /dev/null +++ b/queries/remove_copilot.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_copilot", + "db" : "tcs_catalog", + "sqlfile" : "remove_copilot" +} \ No newline at end of file diff --git a/queries/remove_reviewer b/queries/remove_reviewer new file mode 100755 index 000000000..27d8b3b9e --- /dev/null +++ b/queries/remove_reviewer @@ -0,0 +1 @@ +DELETE FROM rboard_user WHERE user_id = @userId@ AND project_type_id = @categoryId@ \ No newline at end of file diff --git a/queries/remove_reviewer.json b/queries/remove_reviewer.json new file mode 100755 index 000000000..db152c918 --- /dev/null +++ b/queries/remove_reviewer.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_reviewer", + "db" : "tcs_catalog", + "sqlfile" : "remove_reviewer" +} \ No newline at end of file diff --git a/routes.js b/routes.js index e9d94651f..57af4d99d 100755 --- a/routes.js +++ b/routes.js @@ -1,9 +1,9 @@ /* - * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. + * Copyright (C) 2013 - 2016 TopCoder Inc., All Rights Reserved. * - * @version 1.68 + * @version 1.69 * @author vangavroche, Sky_, muzehyun, kurtrips, Ghost_141, ecnu_haozi, hesibo, LazyChild, isv, flytoj2ee, - * @author panoptimum, bugbuka, Easyhard, TCASSEMBLER + * @author panoptimum, bugbuka, Easyhard, TCASSEMBLER,TCSCODER * * Changes in 1.1: * - add routes for search challenges @@ -156,6 +156,18 @@ * - Added get user develop challenges api. * Changed in 1.68: * - Added get rounds api. + * Changed in 1.69: + * - Added routes for reviewer management api: + * - Add Reviewer + * - Remove Reviewer + * - Get All Reviewers + * - Add Copilot + * - Remove Copilot + * - Get All Copilots + * - Create Admin + * - Remove Admin + * - Get All Admins + */ /*jslint node:true, nomen: true */ "use strict"; @@ -361,6 +373,11 @@ exports.routes = { { path: "/:apiVersion/auth0/callback", action: "auth0Callback" }, { path: "/:apiVersion/data/rounds", action: "getRounds" }, + //Admin App - TC API Reviewer Management API + { path: "/:apiVersion/admin/admins", action: "admins" }, + { path: "/:apiVersion/admin/copilots", action: "copilots" }, + { path: "/:apiVersion/admin/reviewers", action: "reviewers" }, + //Stubs APIs { path: "/:apiVersion/data/reviewOpportunities/:id", action: "getAlgorithmsReviewOpportunity" }, { path: "/:apiVersion/data/reviewOpportunities", action: "getAlgorithmsReviewOpportunities" }, @@ -407,7 +424,12 @@ exports.routes = { { path: "/:apiVersion/data/srm/rounds/:roundId/terms", action: "setRoundTerms"}, { path: "/:apiVersion/data/srm/rounds", action: "createSRMContestRound" }, { path: "/:apiVersion/src2image", action: "convertSourceCodeToImage" }, - { path: "/:apiVersion/dump", action: "dumpMemory"} + { path: "/:apiVersion/dump", action: "dumpMemory"}, + + //Admin App - TC API Reviewer Management API + { path: "/:apiVersion/admin/admins", action: "createAdmin" }, + { path: "/:apiVersion/admin/copilots", action: "createCopilot" }, + { path: "/:apiVersion/admin/reviewers", action: "createReviewer" } ], put: [ @@ -418,6 +440,11 @@ exports.routes = { delete: [ { path: "/:apiVersion/data/srm/rounds/:questionId/question", action: "deleteRoundQuestion" }, { path: "/:apiVersion/data/srm/rounds/:roundId", action: "deleteSRMContestRound" }, - { path: "/:apiVersion/data/srm/answer/:answerId", action: "deleteRoundQuestionAnswer" } + { path: "/:apiVersion/data/srm/answer/:answerId", action: "deleteRoundQuestionAnswer" }, + + //Admin App - TC API Reviewer Management API + { path: "/:apiVersion/admin/admins", action: "removeAdmin" }, + { path: "/:apiVersion/admin/copilots", action: "removeCopilot" }, + { path: "/:apiVersion/admin/reviewers", action: "removeReviewer" } ] }; diff --git a/test/docker/docker-compose.yml b/test/docker/docker-compose.yml new file mode 100755 index 000000000..9124c1379 --- /dev/null +++ b/test/docker/docker-compose.yml @@ -0,0 +1,6 @@ +version: '2' +services: + tc-informix: + image: appiriodevops/informix:1b3d4ef + ports: + - "2021:2021" diff --git a/test/postman/Reviewer_Management_API.json b/test/postman/Reviewer_Management_API.json new file mode 100755 index 000000000..cd7d0837e --- /dev/null +++ b/test/postman/Reviewer_Management_API.json @@ -0,0 +1,2041 @@ +{ + "id": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "name": "Admin App - TC API Reviewer Management API", + "description": "", + "order": [], + "folders": [ + { + "id": "142d5b1b-b304-bced-c1f3-b59e8187d2aa", + "name": "create admin", + "description": "", + "order": [ + "17cd6d9b-17b2-1f5c-a0d8-e0948c718d26", + "fc7d7ac4-d1e1-a441-6717-ecb50b9ad7f6", + "9bccd2a2-8aac-1931-b78e-8b61a99415a4", + "8b843cb6-1fc6-8c0b-1d6d-f54d9470cf76", + "88ae8323-4232-1675-8796-e06c6997f3f9", + "2297e0e7-4871-b68e-6258-b59a1da3acd4", + "59d2ab3f-1ef2-3925-42c4-754eced98658", + "43dbe6f5-32e5-f0f0-5735-42a3a9f08120" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "8052cbf5-e206-af03-0392-e853f3f06bf4", + "name": "create copilot", + "description": "", + "order": [ + "3a87b2a7-3761-5089-38d9-6c7d3796e984", + "2cb65342-ba7f-b473-6afc-8a729da04563", + "e058d344-3c5d-951a-c86e-ebfeba41bab8", + "42fc7559-c2a2-8b85-67e3-3812ae0d5998", + "518db876-c166-6aa7-d484-b8b778297b4e", + "9fb05ea1-f85e-209e-7619-e40450acd7f3", + "79444594-2c40-1e6e-9f71-f22d57828026", + "26e53a5b-bef9-17f2-fb02-34595bf8a8c4", + "9ff114f0-56ca-f56c-08e4-3bb4dcaac10f", + "6727946e-aa76-0996-e734-800bb07cf5c6", + "49c5d6f4-5b86-3e9f-8c82-ef7430411264", + "858050d9-d3f2-900f-79a8-4a56c7ca586b", + "68d189c2-eb05-ded4-1e97-414ebde03ab8", + "34ac4817-1082-2274-ed98-f17b63e62786", + "477195d0-14ca-6a6e-274f-513e7d50a45c" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "7544e9b2-c615-4376-7145-511bb968c906", + "name": "create reviewer", + "description": "", + "order": [ + "ebcf31ae-035b-49ac-41d4-fa4484eff6a1", + "b84bd2e1-cd0b-3489-3c11-917a1624249e", + "4a6e3f4b-cb9e-978d-281f-81a57545b0ac", + "ac18e672-1e47-37fa-1ae8-85eed1beab2d", + "202e4545-829b-f158-372f-206e632d609f", + "8679c69e-e94d-7188-60fd-bbe506b851d1", + "2f652b9b-7dc4-8140-9ac5-e6995f1a3fba", + "a8e7c6c2-1cfb-e3ad-af5c-9c27bd71924b", + "d0a6e94f-3c59-2398-f0f5-a1d54a6f4e15", + "03ce940d-9b2d-b46b-0f84-48da5529615c", + "24bc8058-d1ef-1f33-eb64-ede9b9663f49", + "84cd43c0-c407-365b-f1f4-07929e2876cc", + "8a43eff3-3f9a-83a2-6734-a683e29252c8", + "790064a6-e429-1f9c-24b5-7112ce1fb8b2", + "0865091c-8f04-f18c-f9e5-e5c182bc8ab3", + "9e458d5b-5aa5-05ec-6240-7d171c75cf5e", + "0e55b155-b8b7-ae58-5505-f1ff9ba0eb36", + "a466f2b7-2f9c-4a14-fc0e-eee7981cdb42", + "16044f47-5b3d-2ba2-1787-8e6f7e78af97" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "2964b89d-893b-aada-2e0e-c4136b920508", + "name": "get all admins", + "description": "", + "order": [ + "b0bfa529-5f58-d5a6-e1bc-2099abfa253f", + "b83cdcb1-5abe-0f73-1c9c-b558c0634baf", + "144d7cd5-cedb-1e15-fec5-27c5e56863df", + "e10c59ae-1669-54bd-03bb-4de19d51fb5e", + "828d7e47-ee45-5ed2-fe37-2114815e84f1" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "1a80812c-dfa2-5dc4-aca7-299502bbc807", + "name": "get all copilots", + "description": "", + "order": [ + "67db2b5a-4b85-4e18-2709-5c475849329f", + "2d18170b-9631-3c97-fc68-51d0ed76766c", + "a47c9a54-d12a-6801-02a1-c57c420237cf", + "21bdc1e1-c5b2-d063-fd54-386ab396b224", + "897d4baa-72d5-1eca-ff61-51db0d14408c" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "81516934-d74e-f97d-262f-21d87d5961d1", + "name": "get all reviewers", + "description": "", + "order": [ + "08364378-8daf-8159-547e-fac22ca27847", + "c28b4a81-8ee7-24ba-fc8b-54b1b3642fab", + "8bd1727c-34f8-ced7-eb92-d50cc6e56772", + "fe0a03f9-4969-651a-5eed-01de9398498e", + "ddf839fa-733a-056a-8a2c-a1e56d0e9072", + "ef7fd1f5-a302-b7b6-a772-a43eb3b82062", + "c0db4362-622a-b556-1991-80df568707b7", + "f784d0d8-8645-7633-91c6-54ec81aa95ff", + "c1d0d1a9-a8b9-8ee7-c6e6-e539a52fbf35" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "fedff379-68f7-3322-d2e9-29471d82cc60", + "name": "login", + "description": "", + "order": [ + "f6c44b3f-570d-e48f-3b7e-8419a9ebe9b6", + "a495567b-a450-037f-ca8e-c9da52116890" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea", + "name": "remove admin", + "description": "", + "order": [ + "d2bf20ce-e0ed-a347-69dc-577e34dbefd0", + "07358622-f7d4-9233-8dc1-204acb7b1ccf", + "3f0e98fb-e97a-e6ed-8dc7-e864b1447e03", + "c8075e84-bb8d-7596-ba92-5990705f93bc", + "b8d3fef6-214c-5032-3002-f6c16f3288e2", + "2f633003-2ac7-99ec-e52a-8b1f2e0534d4", + "600d723c-706d-42ca-9ecc-94a32e280063", + "1e4fffd8-a809-e9dc-0659-0df467954407" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "2afb4105-1932-6ef0-866c-43ecb13c0048", + "name": "remove copilot", + "description": "", + "order": [ + "ccc25e86-5365-aaf6-8ce8-9838898142eb", + "85022108-999a-7200-89cd-e6b11f66bcb8", + "357caf52-98e8-90a9-8d13-1bf8d330915e", + "34b27e13-3b96-0a9e-25e6-8a339c517518", + "49952d3d-864a-25ff-3648-abbb79550dc2", + "ad152575-1e23-f242-96d2-f4b49a616c56", + "538a99a4-4701-1ee0-527c-6d2db316dae6", + "19a2adaa-d44b-5fd7-9ee7-a6d3452e4492" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "f11d599f-4472-bcef-b9e3-7c86ac139e35", + "name": "remove reviewer", + "description": "", + "order": [ + "49557532-d98e-55e3-5cc4-4c702b62e3ed", + "3453a16f-6a34-c92c-91ed-d2107b505e7a", + "c3383672-4018-ec1b-2688-515b73987d36", + "636f8c2c-7cc2-4d90-179e-7808ae8bbba1", + "a887c7eb-8b15-90aa-07ef-6d998297bcee", + "e6b6464b-da41-67ee-c2ca-53b1d5ba417d", + "4b8d4933-247c-d204-23c6-3acd8ec2e66a", + "da88d593-5117-3c1f-f56c-47ac74724c79", + "13c71689-cfe5-512d-f5b6-d971fa88129a", + "6fd784f5-bda9-b183-1d85-c30879a47427", + "37b02c2b-7018-6af5-4e3c-57b2abde2b0e", + "05ed43fe-61be-43b3-36c7-0c4a12f98efd", + "12d79b9a-e008-ea3d-6fa4-21c7e72e4e94", + "6fcaeb1e-8611-9adf-319f-92f61ac2d62d" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + } + ], + "timestamp": 1474156790593, + "owner": 0, + "public": false, + "published": false, + "hasRequests": true, + "requests": [ + { + "id": "03ce940d-9b2d-b46b-0f84-48da5529615c", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474188017748, + "name": "create reviewer with invalid categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": \"wrong number\"\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "05ed43fe-61be-43b3-36c7-0c4a12f98efd", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189158880, + "name": "remove reviewer with non-integer categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 1.1\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "07358622-f7d4-9233-8dc1-204acb7b1ccf", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173280471, + "name": "remove admin with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "08364378-8daf-8159-547e-fac22ca27847", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474268286128, + "name": "get all reviewers with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "0865091c-8f04-f18c-f9e5-e5c182bc8ab3", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474268192451, + "name": "create reviewer with invalid immune", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"Yoshi\",\n \"categoryId\": 14,\n \"immune\": \"invalid boolean\"\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "0e55b155-b8b7-ae58-5505-f1ff9ba0eb36", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282763352, + "name": "create reviewer with code category id", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"Hung\",\n \"categoryId\": 39\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "12d79b9a-e008-ea3d-6fa4-21c7e72e4e94", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189177783, + "name": "remove reviewer without categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "13c71689-cfe5-512d-f5b6-d971fa88129a", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282495801, + "name": "remove reviewer with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "144d7cd5-cedb-1e15-fec5-27c5e56863df", + "headers": "", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474174192935, + "name": "get all admins without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "16044f47-5b3d-2ba2-1787-8e6f7e78af97", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers?categoryId=14", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474284611393, + "name": "create reviewer with categoryId in query and body at same time", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\":7\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "17cd6d9b-17b2-1f5c-a0d8-e0948c718d26", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474168970336, + "name": "create admin with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "19a2adaa-d44b-5fd7-9ee7-a6d3452e4492", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177129491, + "name": "remove copilot with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "1e4fffd8-a809-e9dc-0659-0df467954407", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173533574, + "name": "remove admin with notexist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "202e4545-829b-f158-372f-206e632d609f", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187893955, + "name": "create reviewer without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "21bdc1e1-c5b2-d063-fd54-386ab396b224", + "headers": "Authorization: wrong\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176769090, + "name": "get all copilots without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "2297e0e7-4871-b68e-6258-b59a1da3acd4", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474169000065, + "name": "create admin without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "24bc8058-d1ef-1f33-eb64-ede9b9663f49", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474188046380, + "name": "create reviewer with nagative categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": -1\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "26e53a5b-bef9-17f2-fb02-34595bf8a8c4", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474178883094, + "name": "create copilot without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "2cb65342-ba7f-b473-6afc-8a729da04563", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179347234, + "name": "create copilot with isSoftwareCopilot false", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": 0,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "2d18170b-9631-3c97-fc68-51d0ed76766c", + "headers": "Authorization: Bearer {{userToken}}\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176739711, + "name": "get all copilots with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "2f633003-2ac7-99ec-e52a-8b1f2e0534d4", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173290943, + "name": "remove admin without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "2f652b9b-7dc4-8140-9ac5-e6995f1a3fba", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187901930, + "name": "create reviewer without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "3453a16f-6a34-c92c-91ed-d2107b505e7a", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189053598, + "name": "remove reviewer with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "34ac4817-1082-2274-ed98-f17b63e62786", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179249694, + "name": "create copilot with invalid isStudioCopilot", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"isStudioCopilot\": \"invalid boolean\",\n \"isSoftwareCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "34b27e13-3b96-0a9e-25e6-8a339c517518", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177113168, + "name": "remove copilot without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "357caf52-98e8-90a9-8d13-1bf8d330915e", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177110084, + "name": "remove copilot without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "37b02c2b-7018-6af5-4e3c-57b2abde2b0e", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189145913, + "name": "remove reviewer with negative categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": -1\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "3a87b2a7-3761-5089-38d9-6c7d3796e984", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474272371352, + "name": "create copilot with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "3f0e98fb-e97a-e6ed-8dc7-e864b1447e03", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173276705, + "name": "remove admin without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "42fc7559-c2a2-8b85-67e3-3812ae0d5998", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177780363, + "name": "create copilot with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "43dbe6f5-32e5-f0f0-5735-42a3a9f08120", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173399023, + "name": "create admin with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "477195d0-14ca-6a6e-274f-513e7d50a45c", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179290066, + "name": "create copilot with isStudioCopilot/isSoftwareCopilot false", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isStudioCopilot\": false,\n \"isSoftwareCopilot\":false\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "49557532-d98e-55e3-5cc4-4c702b62e3ed", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474266891365, + "name": "remove reviewer with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "49952d3d-864a-25ff-3648-abbb79550dc2", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177116518, + "name": "remove copilot with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "49c5d6f4-5b86-3e9f-8c82-ef7430411264", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179169639, + "name": "create copilot with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "4a6e3f4b-cb9e-978d-281f-81a57545b0ac", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187884890, + "name": "create reviewer with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "4b8d4933-247c-d204-23c6-3acd8ec2e66a", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189091821, + "name": "remove reviewer without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "518db876-c166-6aa7-d484-b8b778297b4e", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474178853689, + "name": "create copilot without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "538a99a4-4701-1ee0-527c-6d2db316dae6", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177126290, + "name": "remove copilot with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "59d2ab3f-1ef2-3925-42c4-754eced98658", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474169674465, + "name": "create admin with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "600d723c-706d-42ca-9ecc-94a32e280063", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173326368, + "name": "remove admin without empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "636f8c2c-7cc2-4d90-179e-7808ae8bbba1", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189062608, + "name": "remove reviewer without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "6727946e-aa76-0996-e734-800bb07cf5c6", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189246551, + "name": "create copilot without isStudioCopilot", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "67db2b5a-4b85-4e18-2709-5c475849329f", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176736874, + "name": "get all copilots with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "68d189c2-eb05-ded4-1e97-414ebde03ab8", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179220315, + "name": "create copilot with invalid isSoftwareCopilot", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"isSoftwareCopilot\": \"invalid boolean\",\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "6fcaeb1e-8611-9adf-319f-92f61ac2d62d", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers?categoryId=14", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474284520276, + "name": "remove reviewer with categoryId in query and body at same time", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\":7\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "6fd784f5-bda9-b183-1d85-c30879a47427", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189135829, + "name": "remove reviewer with invalid categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": \"wrong number\"\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "790064a6-e429-1f9c-24b5-7112ce1fb8b2", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474283473763, + "name": "create reviewer with immune", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"Yoshi\",\n \"categoryId\": 14,\n \"immune\":1\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "79444594-2c40-1e6e-9f71-f22d57828026", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474178865110, + "name": "create copilot with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "828d7e47-ee45-5ed2-fe37-2114815e84f1", + "headers": "Authorization: Bearer wrong\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474174234342, + "name": "get all admins with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "84cd43c0-c407-365b-f1f4-07929e2876cc", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474188081763, + "name": "create reviewer with non-integer categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 1.1\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "85022108-999a-7200-89cd-e6b11f66bcb8", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177106806, + "name": "remove copilot with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "858050d9-d3f2-900f-79a8-4a56c7ca586b", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179176872, + "name": "create copilot with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "8679c69e-e94d-7188-60fd-bbe506b851d1", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187897602, + "name": "create reviewer with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "88ae8323-4232-1675-8796-e06c6997f3f9", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474168987848, + "name": "create admin with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "897d4baa-72d5-1eca-ff61-51db0d14408c", + "headers": "Authorization: Bearer wrong\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176766163, + "name": "get all copilots with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "8a43eff3-3f9a-83a2-6734-a683e29252c8", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189200056, + "name": "create reviewer without categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "8b843cb6-1fc6-8c0b-1d6d-f54d9470cf76", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474168982704, + "name": "create admin without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "8bd1727c-34f8-ced7-eb92-d50cc6e56772", + "headers": "", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282217351, + "name": "get all reviewers without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "9bccd2a2-8aac-1931-b78e-8b61a99415a4", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474168977872, + "name": "create admin without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "9e458d5b-5aa5-05ec-6240-7d171c75cf5e", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282760160, + "name": "create reviewer with studio type", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"cartajs\",\n \"categoryId\": 17\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "9fb05ea1-f85e-209e-7619-e40450acd7f3", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474178858906, + "name": "create copilot without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "9ff114f0-56ca-f56c-08e4-3bb4dcaac10f", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189226479, + "name": "create copilot without isSoftwareCopilot", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "a466f2b7-2f9c-4a14-fc0e-eee7981cdb42", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282767080, + "name": "create reviewer with f2f category id", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"liquid_user\",\n \"categoryId\": 38\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "a47c9a54-d12a-6801-02a1-c57c420237cf", + "headers": "", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176772030, + "name": "get all copilots without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "a495567b-a450-037f-ca8e-c9da52116890", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/auth", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "var authResponse = JSON.parse(responseBody);\npostman.setEnvironmentVariable(\"authToken\", authResponse.token);\ntests[\"Status code is 200\"] = responseCode.code === 200;\nvar jsonData = JSON.parse(responseBody);\ntests[\"A valid token is returned\"] = !!jsonData.token;", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474159245944, + "name": "Log in as ordinary user", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"user\", \n \"password\": \"password\"\n}", + "folder": "fedff379-68f7-3322-d2e9-29471d82cc60" + }, + { + "id": "a887c7eb-8b15-90aa-07ef-6d998297bcee", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189071606, + "name": "remove reviewer without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "a8e7c6c2-1cfb-e3ad-af5c-9c27bd71924b", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282655617, + "name": "create reviewer with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "ac18e672-1e47-37fa-1ae8-85eed1beab2d", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187888883, + "name": "create reviewer without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "ad152575-1e23-f242-96d2-f4b49a616c56", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177119734, + "name": "remove copilot without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "b0bfa529-5f58-d5a6-e1bc-2099abfa253f", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173887249, + "name": "get all admins with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "b83cdcb1-5abe-0f73-1c9c-b558c0634baf", + "headers": "Authorization: Bearer {{userToken}}\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474174015997, + "name": "get all admins with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "b84bd2e1-cd0b-3489-3c11-917a1624249e", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers?categoryId=14", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282455411, + "name": "create reviewer with categoryId in query", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "b8d3fef6-214c-5032-3002-f6c16f3288e2", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173287352, + "name": "remove admin with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "c0db4362-622a-b556-1991-80df568707b7", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=-4", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474184022183, + "name": "get all reviewers with negative categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "c1d0d1a9-a8b9-8ee7-c6e6-e539a52fbf35", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189283486, + "name": "get all reviewers without categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "c28b4a81-8ee7-24ba-fc8b-54b1b3642fab", + "headers": "Authorization: Bearer {{userToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282212758, + "name": "get all reviewers with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "c3383672-4018-ec1b-2688-515b73987d36", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers?categoryId=14", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474273020582, + "name": "remove reviewer with categoryId in query", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "c8075e84-bb8d-7596-ba92-5990705f93bc", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173283865, + "name": "remove admin without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "ccc25e86-5365-aaf6-8ce8-9838898142eb", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177185914, + "name": "remove copilot with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "d0a6e94f-3c59-2398-f0f5-a1d54a6f4e15", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282659872, + "name": "create reviewer with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "d2bf20ce-e0ed-a347-69dc-577e34dbefd0", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173258538, + "name": "remove admin with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "da88d593-5117-3c1f-f56c-47ac74724c79", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282491538, + "name": "remove reviewer with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "ddf839fa-733a-056a-8a2c-a1e56d0e9072", + "headers": "Authorization: Bearer wrong\n", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282226901, + "name": "get all reviewers with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "e058d344-3c5d-951a-c86e-ebfeba41bab8", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179507223, + "name": "create copilot with isStudioCopilot false", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": 1,\n \"isStudioCopilot\":0\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "e10c59ae-1669-54bd-03bb-4de19d51fb5e", + "headers": "Authorization: wrong\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474174209799, + "name": "get all admins without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "e6b6464b-da41-67ee-c2ca-53b1d5ba417d", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189083334, + "name": "remove reviewer with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "ebcf31ae-035b-49ac-41d4-fa4484eff6a1", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187872946, + "name": "create reviewer with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "ef7fd1f5-a302-b7b6-a772-a43eb3b82062", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=wrong", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474184004438, + "name": "get all reviewers with invalid categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "f6c44b3f-570d-e48f-3b7e-8419a9ebe9b6", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/auth", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "var authResponse = JSON.parse(responseBody);\npostman.setEnvironmentVariable(\"authToken\", authResponse.token);\ntests[\"Status code is 200\"] = responseCode.code === 200;\nvar jsonData = JSON.parse(responseBody);\ntests[\"A valid token is returned\"] = !!jsonData.token;", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474159263289, + "name": "Login as admin user", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"heffan\", \n \"password\": \"password\"\n}", + "folder": "fedff379-68f7-3322-d2e9-29471d82cc60" + }, + { + "id": "f784d0d8-8645-7633-91c6-54ec81aa95ff", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=1.1", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474188155542, + "name": "get all reviewers with non-integer categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "fc7d7ac4-d1e1-a441-6717-ecb50b9ad7f6", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474172375481, + "name": "create admin with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "fe0a03f9-4969-651a-5eed-01de9398498e", + "headers": "Authorization: wrong\n", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282221279, + "name": "get all reviewers without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + } + ] +} \ No newline at end of file diff --git a/test/postman/Reviewer_Management_API_environment.json b/test/postman/Reviewer_Management_API_environment.json new file mode 100755 index 000000000..ec4342a23 --- /dev/null +++ b/test/postman/Reviewer_Management_API_environment.json @@ -0,0 +1,34 @@ +{ + "id": "a10333e6-0eac-fbd1-136c-b3c8451c9d29", + "name": "Admin App - TC API Reviewer Management API", + "values": [ + { + "key": "url", + "value": "http://localhost:8080/api/v2", + "type": "text", + "enabled": true + }, + { + "key": "adminToken", + "type": "text", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU2IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAxNTkyNjgsImlhdCI6MTQ3NDE1OTI2OH0.KRgW9TxNOEiEu5YdQnXQO1nKFULIuy7JlzDZdq9QFQY", + "enabled": true + }, + { + "key": "userToken", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU4IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAxNzI0MDgsImlhdCI6MTQ3NDE3MjQwOH0.sIG2FoNiCldizzcTMQ9iAFh-PCigNGBAlicxms6uTkk", + "type": "text", + "enabled": true + }, + { + "key": "authToken", + "type": "text", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU4IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAyODI4MDMsImlhdCI6MTQ3NDI4MjgwM30.s6q_FRFryMslkWCkR0wPSWwTopkZhHH8g9R_4GPf9m4", + "enabled": true + } + ], + "timestamp": 1474282803634, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2016-09-19T11:00:24.778Z", + "_postman_exported_using": "Postman/4.7.1" +} \ No newline at end of file diff --git a/test/scripts/bridge.js b/test/scripts/bridge.js new file mode 100755 index 000000000..7a8d90eb3 --- /dev/null +++ b/test/scripts/bridge.js @@ -0,0 +1,134 @@ +/*jslint nomen: true */ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * This is the simple service that provides a "bridge" between a + * client and the database server. It accepts SQL queries and executes them. + * + * Added just to solve a deployment problem. Can (and probably should) be removed + * when this problem will be fixed + * + * @author TCSCODER + * @version 1.0 + */ +"use strict"; + +var http = require("http"), + async = require("async"), + Jdbc = require("informix-wrapper"), + path = require("path"), + tcConfig = require(path.dirname(path.dirname(__dirname)) + "/config/tc-config.js").tcConfig; + +var connections = {}; + +var server = http.createServer(function (req, res) { + + if (req.method !== "POST" || req.url !== "/bridge") { + res.writeHead(404, "Not found"); + res.end(); + return; + } + + var header = {"Content-Type": "application/json"}, + body = []; + + req.on("data", function (chunk) { + body.push(chunk); + }).on("end", function () { + body = Buffer.concat(body).toString(); + + var query; + try { + query = JSON.parse(body); + query.sql = new Buffer(query.sql, "base64").toString(); + } catch (x) { + res.writeHead(400, "Bad request", header); + res.end(JSON.stringify(x.toString())); + return; + } + + if (!query.db) { + res.writeHead(400, "Bad request", header); + res.end("'db' parameter is required"); + return; + } + if (!query.sql) { + res.writeHead(400, "Bad request", header); + res.end("'sql' parameter is required"); + return; + } + try { + async.waterfall([ + function (next) { + var jdbc, prefix, settings; + + if (connections[query.db] && connections[query.db].isConnected()) { + next(null, connections[query.db]); + } else { + jdbc = connections[query.db]; + + if (!jdbc) { + prefix = tcConfig.databaseMapping[query.db]; + if (!prefix) { + res.writeHead(400, "Bad request", header); + res.end(query.db + "- unknown database"); + return; + } + settings = { + user: process.env[prefix + "_USER"], + host: process.env[prefix + "_HOST"], + port: parseInt(process.env[prefix + "_PORT"], 10), + password: process.env[prefix + "_PASSWORD"], + database: query.db, + server: process.env[prefix + "_NAME"], + minpool: parseInt(process.env.MINPOOL, 10) || 1, + maxpool: parseInt(process.env.MAXPOOL, 10) || 60, + maxsize: parseInt(process.env.MAXSIZE, 10) || 0, + idleTimeout: parseInt(process.env.IDLETIMEOUT, 10) || 3600, + timeout: parseInt(process.env.TIMEOUT, 10) || 30000 + }; + jdbc = connections[query.db] = new Jdbc(settings, console.log).initialize(); + } + jdbc.connect(function (err) { + next(err, jdbc); + }); + } + }, + function (connection, next) { + connection.query(query.sql, next).execute(); + } + ], function (err, rows) { + res.writeHead(200, header); + if (err) { + res.end(JSON.stringify({exception: err.toString()})); + } else { + res.end(JSON.stringify({results: rows})); + } + }); + } catch (x) { + res.writeHead(200, header); + res.end(JSON.stringify({exception: x.toString()})); + } + }); + +}); + +/** + * Close database connection when application exit. + */ +function gracefulShutdown() { + var db, conn; + for (db in connections) { + if (connections.hasOwnProperty(db)) { + conn = connections[db]; + if (conn.isConnected()) { + conn.disconnect(); + } + } + } + process.exit(); +} + +process.on('SIGINT', gracefulShutdown); +process.on('SIGTERM', gracefulShutdown); +server.listen(8082); diff --git a/test/sqls/admins/tcs_catalog__clean b/test/sqls/admins/tcs_catalog__clean new file mode 100755 index 000000000..64ed1e235 --- /dev/null +++ b/test/sqls/admins/tcs_catalog__clean @@ -0,0 +1,4 @@ +DELETE FROM user_group_xref WHERE login_id !=132456 AND group_id = 2000115; +DELETE FROM resource_info WHERE resource_id IN (SELECT resource_id FROM resource WHERE resource_role_id = 13 AND project_id IS NULL AND user_id !=132456); +DELETE FROM resource WHERE resource_role_id = 13 AND project_id IS NULL AND user_id !=132456; +DELETE FROM user_role_xref WHERE role_id=2087 AND login_id !=132456; \ No newline at end of file diff --git a/test/sqls/admins/tcs_catalog__insert_test_data b/test/sqls/admins/tcs_catalog__insert_test_data new file mode 100755 index 000000000..169012497 --- /dev/null +++ b/test/sqls/admins/tcs_catalog__insert_test_data @@ -0,0 +1,15 @@ +INSERT INTO user_group_xref VALUES(100000, 20, 2000115, 132456, 1, current); +UPDATE informixoltp:rating SET rating = -1 WHERE coder_id = 20; +INSERT INTO resource (resource_id, resource_role_id, user_id, create_user, create_date, modify_user, modify_date) +VALUES(100000, 13, 20, 132456, current, 132456, current); +INSERT INTO resource_info (resource_id, resource_info_type_id, value, create_user, create_date, modify_user, modify_date) +VALUES(100000, 1 , 20, 132456, current, 132456, current); +INSERT INTO user_role_xref VALUES ((SELECT NVL(MIN(user_role_id), 0) - 1 FROM user_role_xref), 20, 2087, 132456, 1); + +INSERT INTO resource (resource_id, resource_role_id, user_id, create_user, create_date, modify_user, modify_date) +VALUES(100001, 13, 124856, 132456, current, 132456, current); +INSERT INTO resource_info (resource_id, resource_info_type_id, value, create_user, create_date, modify_user, modify_date) +VALUES(100001, 1 , 124856, 132456, current, 132456, current); + + +INSERT INTO user_role_xref VALUES ((SELECT NVL(MIN(user_role_id), 0) - 1 FROM user_role_xref), 124857, 2087, 132456, 1); diff --git a/test/sqls/copilots/tcs_catalog__clean b/test/sqls/copilots/tcs_catalog__clean new file mode 100755 index 000000000..d3aa9c12d --- /dev/null +++ b/test/sqls/copilots/tcs_catalog__clean @@ -0,0 +1 @@ +DELETE FROM copilot_profile; \ No newline at end of file diff --git a/test/sqls/copilots/tcs_catalog__insert_test_data b/test/sqls/copilots/tcs_catalog__insert_test_data new file mode 100755 index 000000000..c99025668 --- /dev/null +++ b/test/sqls/copilots/tcs_catalog__insert_test_data @@ -0,0 +1,6 @@ +INSERT INTO copilot_profile (copilot_profile_id,user_id,copilot_profile_status_id,suspension_count, +reliability,activation_date,show_copilot_earnings,create_user, create_date, +update_user, update_date, is_software_copilot, is_studio_copilot) +VALUES ((select NVL(max(copilot_profile_id), 0) + 1 from copilot_profile), +20 ,1,0,100.00, current,'t', 132456, +current, 132456, current, 't', 't'); \ No newline at end of file diff --git a/test/sqls/reviewers/tcs_catalog__clean b/test/sqls/reviewers/tcs_catalog__clean new file mode 100755 index 000000000..41655f22e --- /dev/null +++ b/test/sqls/reviewers/tcs_catalog__clean @@ -0,0 +1 @@ +DELETE FROM rboard_user WHERE project_type_id IN (7,17,38, 39); \ No newline at end of file diff --git a/test/sqls/reviewers/tcs_catalog__insert_test_data b/test/sqls/reviewers/tcs_catalog__insert_test_data new file mode 100755 index 000000000..7c97f24de --- /dev/null +++ b/test/sqls/reviewers/tcs_catalog__insert_test_data @@ -0,0 +1 @@ +INSERT INTO rboard_user VALUES (20, 7, 4, 100, 1); \ No newline at end of file diff --git a/test/test.admins.js b/test/test.admins.js new file mode 100755 index 000000000..df930b457 --- /dev/null +++ b/test/test.admins.js @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/admins/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; + + +describe('Get Admins API', function () { + this.timeout(60000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + clearDb(done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @return {Object} request + */ + function createRequest(statusCode, authHeader) { + var url = "/v2/admin/admins", + req = request(API_ENDPOINT) + .get(url) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, errorMessage, done) { + createRequest(statusCode, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, file, done) { + createRequest(200, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/admins/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, "You need to login for this api.", done); + + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, "You don\'t have access to this api.", done); + }); + + it("should return admins", function (done) { + assertResponse(adminHeader, "expect_get_admins", done); + }); + +}); diff --git a/test/test.copilots.js b/test/test.copilots.js new file mode 100755 index 000000000..e15ffa518 --- /dev/null +++ b/test/test.copilots.js @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/copilots/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; + + +describe('GET copilots API', function () { + this.timeout(60000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @return {Object} request + */ + function createRequest(statusCode, authHeader) { + var url = "/v2/admin/copilots", + req = request(API_ENDPOINT) + .get(url) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, errorMessage, done) { + createRequest(statusCode, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, file, done) { + createRequest(200, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/copilots/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, "You need to login for this api.", done); + + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, "You don\'t have access to this api.", done); + }); + + it("should return copilots", function (done) { + assertResponse(adminHeader, "expect_get_copilots", done); + }); + +}); diff --git a/test/test.createAdmin.js b/test/test.createAdmin.js new file mode 100755 index 000000000..09bc20d32 --- /dev/null +++ b/test/test.createAdmin.js @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/admins/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester1'; +var testBody = {username: username}; + +describe('Create Admin API', function () { + this.timeout(600000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/admins", + req = request(API_ENDPOINT) + .post(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/admins/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for missing username", function (done) { + assertResponse(adminHeader, {}, "expect_create_admin_with_empty_body", done); + }); + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, {username: ' \n \t \r'}, + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, {username: true}, + "username should be string.", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, {username: 'notexist'}, + "User with the username: notexist does not exist", done); + }); + + it("should return already exist error for exist admin username", function (done) { + assertErrorResponse(409, adminHeader, {username: 'dok_tester'}, + "User dok_tester has already been added to Admin Group", done); + }); + + it("should create admin successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_create_admin", done); + }); + + it("should create admin successfully if exist admin resource", function (done) { + assertResponse(adminHeader, {username: 'wyzmo'}, "expect_create_admin_with_exist_admin_resource", done); + }); + + it("should return admin role exist role if exist admin role", function (done) { + assertErrorResponse(409, adminHeader, {username: 'cartajs'}, + "User cartajs has already been assigned Admin role", done); + }); +}); diff --git a/test/test.createCopilot.js b/test/test.createCopilot.js new file mode 100755 index 000000000..86b9fe953 --- /dev/null +++ b/test/test.createCopilot.js @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var _ = require('underscore'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/copilots/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester1'; +var testBody = { + username: username, + isSoftwareCopilot: true, + isStudioCopilot: false +}; + +describe('Create Copilot API', function () { + this.timeout(600000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/copilots", + req = request(API_ENDPOINT) + .post(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/copilots/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for empty body or missing username", function (done) { + assertResponse(adminHeader, {}, "expect_create_copilot_with_empty_body", done); + }); + + + it("should return required error for missing isSoftwareCopilot", function (done) { + assertResponse(adminHeader, _.omit(testBody, 'isSoftwareCopilot'), + "expect_create_copilot_with_missing_isSoftwareCopilot", done); + }); + + it("should return required error for missing isStudioCopilot", function (done) { + assertResponse(adminHeader, _.omit(testBody, 'isStudioCopilot'), + "expect_create_copilot_with_missing_isStudioCopilot", done); + }); + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: ' \n \t \r'}), + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: true}), + "username should be string.", done); + }); + + it("should return validation error for invalid isStudioCopilot", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {isStudioCopilot: 'invalid boolean'}), + "isStudioCopilot should be 0, 1, true or false.", done); + }); + + it("should return validation error for invalid isSoftwareCopilot", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {isSoftwareCopilot: 'invalid boolean'}), + "isSoftwareCopilot should be 0, 1, true or false.", done); + }); + + it("should return validation error for isSoftwareCopilot/isStudioCopilot false at same time", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), { isStudioCopilot: 0, isSoftwareCopilot: false }), + "Studio Copilot and Software Copilot Checkbox should have at least one checked", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'notexist'}), + "User with the username: notexist does not exist", done); + }); + + it("should return duplicate resource error if exist copilot", function (done) { + assertErrorResponse(409, adminHeader, _.extend(_.clone(testBody), {username: 'dok_tester'}), + "The user dok_tester is already added as copilot", done); + }); + + it("should create copilot successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_create_copilot", done); + }); +}); diff --git a/test/test.createReviewer.js b/test/test.createReviewer.js new file mode 100755 index 000000000..f22b48991 --- /dev/null +++ b/test/test.createReviewer.js @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var _ = require('underscore'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/reviewers/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester1'; +var testBody = { + username: username, + categoryId: 7, + immune: 0 +}; + +describe('Create Reviewer API', function () { + this.timeout(6000000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/reviewers", + req = request(API_ENDPOINT) + .post(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (file) { + var body = res.body, expected = require("./test_files/reviewers/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + } + done(); + }); + } + + /** + * Create reviewer and validate exist such reviewer with immune = true + * @param {Object} postData - the data post to api. + * @param {Boolean} immune - the immune flag + * @param {Function} done - the callback + */ + function assertCreateAndGetAllResponse(postData, immune, done) { + var authHeader = adminHeader; + assertResponse(adminHeader, postData, null, function (err) { + if (err) { + return done(err); + } + request(API_ENDPOINT) + .get("/v2/admin/reviewers?categoryId=" + postData.categoryId) + .set('Accept', 'application/json') + .set('Authorization', authHeader) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var reviewer = _.find(res.body.reviewers, function (data) { + return data.name === postData.username; + }); + assert.ok(reviewer); + assert.equal(reviewer.immune, immune); + done(); + }); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for empty body or missing username", function (done) { + assertResponse(adminHeader, {}, "expect_create_reviewer_with_empty_body", done); + }); + + + it("should return required error for missing categoryId", function (done) { + assertResponse(adminHeader, _.omit(testBody, 'categoryId'), + "expect_create_reviewer_with_missing_categoryId", done); + }); + + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: ' \n \t \r'}), + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: true}), + "username should be string.", done); + }); + + it("should return validation error for invalid categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 'invalid number'}), + "categoryId should be number.", done); + }); + + it("should return validation error for negative categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: -2}), + "categoryId should be positive.", done); + }); + + it("should return validation error for non-integer categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 1.1}), + "categoryId should be Integer.", done); + }); + + it("should return validation error for invalid immune", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {immune: 'invalid boolean'}), + "immune should be 0, 1, true or false.", done); + }); + + it("should return validation error for not valid categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 99999}), + "Category Id 99999 is not a valid category ID", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'notexist'}), + "User with the username: notexist does not exist", done); + }); + + it("should return duplicate resource error if exist reviewer", function (done) { + assertErrorResponse(409, adminHeader, _.extend(_.clone(testBody), {username: 'dok_tester'}), + "User dok_tester is in the specific review board", done); + }); + + it("should create reviewer successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_create_reviewer", done); + }); + + it("should create immune=true reviewer successfully with immune=1 in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), {username: 'wyzmo', immune: 1}), true, done); + }); + + it("should create immune=false reviewer successfully with immune=0 in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), { + username: 'ksmith', + immune: 0 + }), false, done); + }); + + it("should create immune=true reviewer successfully with studio type", function (done) { + // 17 = Web Design = Studio type + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), { + username: 'cartajs', + categoryId: 17 + }), true, done); + }); + + it("should create immune=false reviewer successfully with immune=false in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), + {username: 'Yoshi', categoryId: 17, immune: false}), false, done); + }); + + it("should create immune=true reviewer successfully with code category id in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), + {username: 'Hung', categoryId: 39}), true, done); + }); + + + it("should create immune=true reviewer successfully with f2f category id in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), + {username: 'liquid_user', categoryId: 38}), true, done); + }); + +}); diff --git a/test/test.removeAdmin.js b/test/test.removeAdmin.js new file mode 100755 index 000000000..1904ccb21 --- /dev/null +++ b/test/test.removeAdmin.js @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/admins/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester'; +var testBody = {username: username}; + +describe('Remove Admin API', function () { + this.timeout(600000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/admins", + req = request(API_ENDPOINT) + .del(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/admins/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for missing username", function (done) { + assertResponse(adminHeader, {}, "expect_remove_admin_with_empty_body", done); + }); + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, {username: ' \n \t \r'}, + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, {username: true}, + "username should be string.", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, {username: 'notexist'}, + "User with the username: notexist does not exist", done); + }); + + it("should remove admin successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_remove_admin", done); + }); +}); diff --git a/test/test.removeCopilot.js b/test/test.removeCopilot.js new file mode 100755 index 000000000..48ae05c01 --- /dev/null +++ b/test/test.removeCopilot.js @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var _ = require('underscore'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/copilots/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester'; +var testBody = { + username: username, + isSoftwareCopilot: true, + isStudioCopilot: false +}; + +describe('Remove Copilot API', function () { + this.timeout(600000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/copilots", + req = request(API_ENDPOINT) + .del(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/copilots/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for empty body or missing username", function (done) { + assertResponse(adminHeader, {}, "expect_remove_copilot_with_empty_body", done); + }); + + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: ' \n \t \r'}), + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: true}), + "username should be string.", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'notexist'}), + "User with the username: notexist does not exist", done); + }); + + it("should return not found error if not exist copilot", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'user'}), + "user is not in the copilot pool", done); + }); + + + it("should remove copilot successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_remove_copilot", done); + }); +}); diff --git a/test/test.removeReviewer.js b/test/test.removeReviewer.js new file mode 100755 index 000000000..1290fd1aa --- /dev/null +++ b/test/test.removeReviewer.js @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var _ = require('underscore'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/reviewers/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var testBody = { + username: 'dok_tester', + categoryId: 7 +}; + +describe('Remove Reviewer API', function () { + this.timeout(6000000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/reviewers", + req = request(API_ENDPOINT) + .del(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (file) { + var body = res.body, expected = require("./test_files/reviewers/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + } + done(); + }); + } + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for empty body or missing username", function (done) { + assertResponse(adminHeader, {}, "expect_remove_reviewer_with_empty_body", done); + }); + + + it("should return required error for missing categoryId", function (done) { + assertResponse(adminHeader, _.omit(testBody, 'categoryId'), + "expect_remove_reviewer_with_missing_categoryId", done); + }); + + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: ' \n \t \r'}), + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: true}), + "username should be string.", done); + }); + + it("should return validation error for invalid categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 'invalid number'}), + "categoryId should be number.", done); + }); + + it("should return validation error for negative categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: -2}), + "categoryId should be positive.", done); + }); + + it("should return validation error for non-integer categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 1.1}), + "categoryId should be Integer.", done); + }); + + + it("should return validation error for not valid categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 99999}), + "Category Id 99999 is not a valid category ID", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'notexist'}), + "User with the username: notexist does not exist", done); + }); + + it("should return not found error if not reviewer", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'user'}), + "There is no reviewer with the username:user in category: Architecture", done); + }); + + it("should remove reviewer successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_remove_reviewer", done); + }); +}); diff --git a/test/test.reviewers.js b/test/test.reviewers.js new file mode 100755 index 000000000..e17af97c2 --- /dev/null +++ b/test/test.reviewers.js @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/reviewers/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; + + +describe('Get Reviewers API', function () { + this.timeout(60000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @return {Object} request + */ + function createRequest(statusCode, authHeader) { + var url = "/v2/admin/reviewers?categoryId=7", + req = request(API_ENDPOINT) + .get(url) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, errorMessage, done) { + createRequest(statusCode, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, file, done) { + createRequest(200, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/reviewers/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, "You need to login for this api.", done); + + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, "You don\'t have access to this api.", done); + }); + + it("should return reviewers", function (done) { + assertResponse(adminHeader, "expect_get_reviewers", done); + }); + +}); diff --git a/test/test_files/admins/expect_create_admin.json b/test/test_files/admins/expect_create_admin.json new file mode 100755 index 000000000..6f0dbd086 --- /dev/null +++ b/test/test_files/admins/expect_create_admin.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "dok_tester1 has been successfully added as TopCoder Admin" +} \ No newline at end of file diff --git a/test/test_files/admins/expect_create_admin_with_empty_body.json b/test/test_files/admins/expect_create_admin_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/admins/expect_create_admin_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/admins/expect_create_admin_with_exist_admin_resource.json b/test/test_files/admins/expect_create_admin_with_exist_admin_resource.json new file mode 100755 index 000000000..9653f022b --- /dev/null +++ b/test/test_files/admins/expect_create_admin_with_exist_admin_resource.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "wyzmo has been successfully added as TopCoder Admin" +} \ No newline at end of file diff --git a/test/test_files/admins/expect_get_admins.json b/test/test_files/admins/expect_get_admins.json new file mode 100755 index 000000000..2e3c7cdb5 --- /dev/null +++ b/test/test_files/admins/expect_get_admins.json @@ -0,0 +1,11 @@ +{ + "allAdmins": [ + { + "adminGroup": true, + "adminRole": true, + "id": 132456, + "managerResource": true, + "name": "heffan" + } + ] +} \ No newline at end of file diff --git a/test/test_files/admins/expect_remove_admin.json b/test/test_files/admins/expect_remove_admin.json new file mode 100755 index 000000000..b07185a05 --- /dev/null +++ b/test/test_files/admins/expect_remove_admin.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "TopCoder Admin: dok_tester has been successfully removed" +} \ No newline at end of file diff --git a/test/test_files/admins/expect_remove_admin_with_empty_body.json b/test/test_files/admins/expect_remove_admin_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/admins/expect_remove_admin_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_create_copilot.json b/test/test_files/copilots/expect_create_copilot.json new file mode 100755 index 000000000..4a17102f0 --- /dev/null +++ b/test/test_files/copilots/expect_create_copilot.json @@ -0,0 +1,4 @@ +{ + "message": "Copilot dok_tester1 has been successfully added", + "success": true +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_create_copilot_with_empty_body.json b/test/test_files/copilots/expect_create_copilot_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/copilots/expect_create_copilot_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_create_copilot_with_missing_isSoftwareCopilot.json b/test/test_files/copilots/expect_create_copilot_with_missing_isSoftwareCopilot.json new file mode 100755 index 000000000..a043993ba --- /dev/null +++ b/test/test_files/copilots/expect_create_copilot_with_missing_isSoftwareCopilot.json @@ -0,0 +1,3 @@ +{ + "error": "Error: isSoftwareCopilot is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_create_copilot_with_missing_isStudioCopilot.json b/test/test_files/copilots/expect_create_copilot_with_missing_isStudioCopilot.json new file mode 100755 index 000000000..756831d32 --- /dev/null +++ b/test/test_files/copilots/expect_create_copilot_with_missing_isStudioCopilot.json @@ -0,0 +1,3 @@ +{ + "error": "Error: isStudioCopilot is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_get_copilots.json b/test/test_files/copilots/expect_get_copilots.json new file mode 100755 index 000000000..4a83f8652 --- /dev/null +++ b/test/test_files/copilots/expect_get_copilots.json @@ -0,0 +1,10 @@ +{ + "allCopilots": [ + { + "id": 20, + "name": "dok_tester", + "softwareCopilot": true, + "studioCopilot": true + } + ] +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_remove_copilot.json b/test/test_files/copilots/expect_remove_copilot.json new file mode 100755 index 000000000..7939ccf6e --- /dev/null +++ b/test/test_files/copilots/expect_remove_copilot.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "Copilot dok_tester has been successfully removed" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_remove_copilot_with_empty_body.json b/test/test_files/copilots/expect_remove_copilot_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/copilots/expect_remove_copilot_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_create_reviewer.json b/test/test_files/reviewers/expect_create_reviewer.json new file mode 100755 index 000000000..c209e8f5e --- /dev/null +++ b/test/test_files/reviewers/expect_create_reviewer.json @@ -0,0 +1,4 @@ +{ + "message": "dok_tester1 has been successfully added into Architecture Review Board", + "success": true +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_create_reviewer_with_empty_body.json b/test/test_files/reviewers/expect_create_reviewer_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/reviewers/expect_create_reviewer_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_create_reviewer_with_missing_categoryId.json b/test/test_files/reviewers/expect_create_reviewer_with_missing_categoryId.json new file mode 100755 index 000000000..1763c9a49 --- /dev/null +++ b/test/test_files/reviewers/expect_create_reviewer_with_missing_categoryId.json @@ -0,0 +1,3 @@ +{ + "error": "Error: categoryId is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_get_reviewers.json b/test/test_files/reviewers/expect_get_reviewers.json new file mode 100755 index 000000000..2e9e8b631 --- /dev/null +++ b/test/test_files/reviewers/expect_get_reviewers.json @@ -0,0 +1,12 @@ +{ + "categoryId":7, + "reviewers": [ + { + "id": 20, + "name": "dok_tester", + "projectCategoryId": 7, + "projectCategoryName": "Architecture", + "immune": true + } + ] +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_remove_reviewer.json b/test/test_files/reviewers/expect_remove_reviewer.json new file mode 100755 index 000000000..4eaad6ebc --- /dev/null +++ b/test/test_files/reviewers/expect_remove_reviewer.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "dok_tester has been successfully removed from Architecture Review Board" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_remove_reviewer_with_empty_body.json b/test/test_files/reviewers/expect_remove_reviewer_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/reviewers/expect_remove_reviewer_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_remove_reviewer_with_missing_categoryId.json b/test/test_files/reviewers/expect_remove_reviewer_with_missing_categoryId.json new file mode 100755 index 000000000..1763c9a49 --- /dev/null +++ b/test/test_files/reviewers/expect_remove_reviewer_with_missing_categoryId.json @@ -0,0 +1,3 @@ +{ + "error": "Error: categoryId is a required parameter for this action" +} \ No newline at end of file From 2236bae7fc5b825f306fe36ee804d566370a3fb0 Mon Sep 17 00:00:00 2001 From: Mauricio Desiderio Date: Wed, 28 Sep 2016 14:53:44 -0500 Subject: [PATCH 09/11] fixing bug that allowed limits of more than 100 records to be retrieved by the get top members api --- .gitignore | 1 + actions/.challengeTypes.js.swp | Bin 0 -> 16384 bytes actions/.challenges.js.swp | Bin 0 -> 196608 bytes actions/tops.js | 3 - deploy/.env.sh.swp | Bin 0 -> 12288 bytes initializers/.dataAccess.js.swp | Bin 0 -> 28672 bytes local/docker-compose.yml | 13 ++++- local/env.sh | 100 ++++++++++++++++++++++++++++++++ local/node/Dockerfile | 15 +++++ queries/get_top_members_data | 3 +- 10 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 actions/.challengeTypes.js.swp create mode 100644 actions/.challenges.js.swp create mode 100644 deploy/.env.sh.swp create mode 100644 initializers/.dataAccess.js.swp create mode 100644 local/env.sh create mode 100644 local/node/Dockerfile diff --git a/.gitignore b/.gitignore index fda4d8551..6b610a3e1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ test/tmp/design_tmp_submissions/*.zip test/tmp/memberPhoto/* .idea .settings +**/jdk-8u51-linux-x64.gz diff --git a/actions/.challengeTypes.js.swp b/actions/.challengeTypes.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..072ff886b8427e7a6c2f4983e272628b6076a22b GIT binary patch literal 16384 zcmeI2UuYaf9LLAn*7~O^S|MPS>GdI(aJ?i=sD?oT2N`>14P#oVn3At`Z5J~p_e zdbPZLv9n2pUY~GcPX$q*Qy0E3gPI(l31w717G15dy=N7$3f!auYuVuDfqpWwv9N*P zf8SF#Y0nn73RnfK0#*U5fK|XMU=^?mSOu1o0&%#4y?_R<$Qm7FueWr(-kN>J*8=8J z*=sXj_R+pr1*`&A0jq#jz$#!BunJfOtO8a6tAJI&D)2v4K)8$*(XKyeBah$zoBRJi z?_un7@EQ0JyauMh0q{8J0_(tPunOD)&fLw|r=SkjgR^V#9&~|q;FG%;^T81K;!eii z0MCJ4;9;;9e0K-x1#f{_@CdkaJ7X8YJK%NjJU9XdK@TVb4juyQK^OQHUsU`IE`X20 zY48#Vz|-IW=m%Y3B{+8*V;_K1;4pX)oLSA-M?e7yCc!gc0*rtjum)UN#n=zvD{vOP z2VMo!U>Dc`ep|`dm*7pX6%@gb_)_Ita2Z?x=fOGfGI$YGzz*;LSPg!`mo(pi^Wc4O z8k_=8f*wF)`1|#LA;am?Nx--9Y7&?;8N7(|871zhAdn`Z!~;1U_wZRpnOzZ!iil){ zA`{)Q6HPiu@l~z+Xvt)VwFsj0cn7j{Ybo6B67TEdaovk}b_4I|#wHc`Gp5v()DaGZ zP>HF{-Kiy >BNJKZDu_KuGp8lOCfe_81)GqqWT zbl61r$T@M;R7a^=o=7|`U6Yzx_5xH$Je~ed^<~13|UQpw$6EZxeBQhm^vhG0+qb6jm zB|^LCL}iBW8&2Xn>+9`hY;L>h=3J|@?RV65jBvRweF#~ui@^2eUNzDh?qKUb)~<-`Qd8kw#lq+`6ptm3;>7io zIWDvjo?9sKZdX>4T6a&opjYL^rE6=t@^bV0im#mG(rqheM98`CKko8{oY;l*5y?Eg z<4()L+k<2LdignGRlv8e(-ZUaf%Qd9PXDei=H>46#*TX}f6S-f;7mJzCdKT+Xg;EA z{$xt4V{W!d=d#>nL?v>Qk(N=2laCUu(K2q&S;lZjv8B=|-EA*U+qf6YMmd+lC`ac- zk8YNg-lorUXQtM2LzByxo8@n%PSPSY>ABY^JR)LQQ+j3*0|*z)twoJMk#d+s_2Mkc zHw>C3eq6?~I4Ycau^p;Vznjx}v%r+lq%8EEI67b6)>^V#X)NR}vXNaZAqBI96ms$} zy2xG6P%l_pN3~XZ;c6(g&Q(V%7Nz7?)mCepD_`1L>RX}%=`LtC6`=5J*l}bOm1VkM z9!R8~DHgCkPtG632nM1P{(hn13^C%b5?`<^x0OcYN||2GM$Rit4;K%$I)luU!ORs#^Fxhjwp z;5F^-Qp%Sim}~cVlQCNLMGd7z45>qmw+s-D!Z%PyB!zbBS@_c#6_)eH_f=>R<~m2y zRL~6Hb2Aceh|olwF``cdwHlS8`V)za;>FCi=dx6XR^Z5$srW{0VQ7pIdd*6k-b1n^ zf}H5LFy0JNB(M3Zf+*e#s#-*d7#%zaBrCOYoOT!ZqkJvpjV)|B5)94^d5O8*dJNKt zx|~k*1yl%iwdceIOdIL=54&ztadJ`A5WobgN%t|S?68Le>=CbO)!+l=0cuV|>KfXA z(u?bSk8D)n6bEI^gMC4J5sLCD+s=-%{U%W$j>isQ3TDNWR!$uONkbkNf%k0ENKBUI zekrYvl15ZGqrF-^=Ead7zPW#3kdLcyM7gMXEO5#tKJ5GaAmxbAmPqO;=~CYr?f>7# zUi}iF{Xe$C=DLji{AsWq^n#yo|9kLtmiHp%x0hAGDqt0`3RnfK0#*U5fK|XMU=^?m YSOxw|1(w|}V1M_AhwO*QoA~|VFLnG9u>b%7 literal 0 HcmV?d00001 diff --git a/actions/.challenges.js.swp b/actions/.challenges.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..66275bc8f4dfa0aaac9cd1e94b8ec779e0fa94a3 GIT binary patch literal 196608 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 Kof&8JQ1~yqE^jpe literal 0 HcmV?d00001 diff --git a/initializers/.dataAccess.js.swp b/initializers/.dataAccess.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..542d73f7eba4b7e4272e91f9d0283b665d384cbe GIT binary patch literal 28672 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 literal 0 HcmV?d00001 diff --git a/local/docker-compose.yml b/local/docker-compose.yml index 986dbdedb..d37d536f0 100644 --- a/local/docker-compose.yml +++ b/local/docker-compose.yml @@ -1,6 +1,15 @@ version: '2' services: + informix: + image: appiriodevops/informix:1.2 + ports: + - 2021:2021 tc-api: - image: "node" + build: ./node + depends_on: + - informix ports: - - "7777:7777" + - "8080:8080" + volumes: + - ../:/tc-api + command: /bin/bash -c "cd /tc-api && npm install && source local/env.sh && npm start && tail -f log/forever.log" diff --git a/local/env.sh b/local/env.sh new file mode 100644 index 000000000..ac4e6ab9c --- /dev/null +++ b/local/env.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +# +# Copyright (C) 2013-2014 TopCoder Inc., All Rights Reserved. +# +# Version: 1.3 +# Author: vangavroche, isv, TCASSEMBLER +# changes in 1.1: +# - add JIRA_USERNAME and JIRA_PASSWORD +# changes in 1.2: +# - added RESET_PASSWORD_TOKEN_CACHE_EXPIRY environment variable +# - added RESET_PASSWORD_TOKEN_EMAIL_SUBJECT environment variable +# - added REDIS_HOST environment variable +# - added REDIS_PORT environment variable +# changes in 1.3 +# - added WKHTMLTOIMAGE_COMMAND_PATH environment variable +# - added WKHTMLTOIMAGE_IMAGE_WIDTH environment variable +# - added HIGHLIGHT_STYLE_LINK environment variable +# + +# tests rely on caching being off. But set this to a real value (or remove) while coding. +export CACHE_EXPIRY=-1 + +VM_IP=informix +if [ -n "$TC_VM_IP" ] +then +VM_IP=$TC_VM_IP +fi + +export TC_DB_NAME=informixoltp_tcp +export TC_DB_HOST=$VM_IP +export TC_DB_PORT=2021 +export TC_DB_USER=informix +export TC_DB_PASSWORD=1nf0rm1x + +export TC_DW_NAME=informixoltp_tcp +export TC_DW_HOST=$VM_IP +export TC_DW_PORT=2021 +export TC_DW_USER=informix +export TC_DW_PASSWORD=1nf0rm1x + +# oauth provider +export TC_API_HOST=api.topcoder.com + +# LDAP settings +export TC_LDAP_HOST=$VM_IP +export TC_LDAP_PORT=636 +export TC_LDAP_PASSWORD=secret +export TC_LDAP_MEMBER_BASE_DN="ou=members, dc=topcoder, dc=com" +export TC_BIND_DN="cn=Manager,dc=topcoder,dc=com" + +# Mail settings +export TC_EMAIL_HOST=smtp.gmail.com +export TC_EMAIL_HOST_PORT=465 +export TC_EMAIL_SECURED=true +export TC_EMAIL_ACCOUNT=tc.ldap.test.1@gmail.com +export TC_EMAIL_PASSWORD=tc_public_email +export TC_EMAIL_FROM=tc.ldap.test.1@gmail.com +export TC_EMAIL_TEMPLATE_DIR=mail_templates + +export TC_ACTIVATION_SERVER_NAME="https://www.topcoder.com" +export TC_SOFTWARE_SERVER_NAME="https://software.topcoder.com" +export TC_FORUMS_SERVER_NAME="http://apps.topcoder.com/forums" + +export PASSWORD_HASH_KEY="ciTHHTSMg6ixffIuPbB30A==" +## JDBC connection pool environment variables - set for all databases +export MINPOOL=1 +export MAXPOOL=20 +export MAXSIZE=0 +export IDLETIMEOUT=3600 +export TIMEOUT=30000 + +# Used in Jira soap service (Bugs API) +export JIRA_USERNAME=api_test +export JIRA_PASSWORD=8CDDp6BHLtUeUdD + +# Forum settings +export STUDIO_FORUMS_SERVER_NAME="http://studio.topcoder.com/forums" +export GRANT_FORUM_ACCESS=false +export DEV_FORUM_JNDI=jnp://env.topcoder.com:1199 + +## The period for expiring the generated tokens for password resetting +export RESET_PASSWORD_TOKEN_EMAIL_SUBJECT=TopCoder Account Password Reset +# Set this to 180000 which is 3 mins. This will help saving time for test. +export RESET_PASSWORD_TOKEN_CACHE_EXPIRY=180000 + +export REDIS_HOST=localhost +export REDIS_PORT=6379 + +export DEVELOP_SUBMISSION_MAX_SIZE=6144 + +export WATERMARK_FILE_PATH=test/test_files/design_image_file_generator/studio_logo_watermark.png + +export WKHTMLTOIMAGE_COMMAND_PATH=/home/ubuntu/tmp/wkhtmltox-0.12.1/static-build/posix-local/wkhtmltox-0.12.1/bin/wkhtmltoimage +export WKHTMLTOIMAGE_IMAGE_WIDTH=1024 +export HIGHLIGHT_STYLE_LINK=http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css + +export JWT_TOKEN_COOKIE_KEY="tcjwt_vm" + +export ADMIN_API_KEY=1234567 diff --git a/local/node/Dockerfile b/local/node/Dockerfile new file mode 100644 index 000000000..f94975902 --- /dev/null +++ b/local/node/Dockerfile @@ -0,0 +1,15 @@ +FROM node:0.10 + +COPY jdk-8u51-linux-x64.gz /opt + +RUN cd /opt && \ + tar -xvf jdk-8u51-linux-x64.gz && \ + cd /usr/bin && \ + ln -s /opt/jdk1.8.0_51/bin/java java && \ + ln -s /opt/jdk1.8.0_51/bin/javac javac + +ENV JAVA_HOME=/opt/jdk1.8.0_51 + +RUN npm install -g java + +RUN apt-get install -y net-tools psmisc diff --git a/queries/get_top_members_data b/queries/get_top_members_data index 7b7ae710c..bf65f0a23 100644 --- a/queries/get_top_members_data +++ b/queries/get_top_members_data @@ -1,5 +1,6 @@ SELECT -FIRST @pageSize@ + SKIP @firstRowIndex@ + FIRST @pageSize@ c.coder_id AS user_id , handle , rating From d798cc51293e4d97e7a9b57a99bd433302dfeb26 Mon Sep 17 00:00:00 2001 From: Mauricio Desiderio Date: Wed, 28 Sep 2016 14:55:03 -0500 Subject: [PATCH 10/11] removing vi temp files --- .gitignore | 1 + actions/.challengeTypes.js.swp | Bin 16384 -> 0 bytes actions/.challenges.js.swp | Bin 196608 -> 0 bytes 3 files changed, 1 insertion(+) delete mode 100644 actions/.challengeTypes.js.swp delete mode 100644 actions/.challenges.js.swp diff --git a/.gitignore b/.gitignore index 6b610a3e1..d5f8cf869 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ test/tmp/memberPhoto/* .idea .settings **/jdk-8u51-linux-x64.gz +*.swp diff --git a/actions/.challengeTypes.js.swp b/actions/.challengeTypes.js.swp deleted file mode 100644 index 072ff886b8427e7a6c2f4983e272628b6076a22b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI2UuYaf9LLAn*7~O^S|MPS>GdI(aJ?i=sD?oT2N`>14P#oVn3At`Z5J~p_e zdbPZLv9n2pUY~GcPX$q*Qy0E3gPI(l31w717G15dy=N7$3f!auYuVuDfqpWwv9N*P zf8SF#Y0nn73RnfK0#*U5fK|XMU=^?mSOu1o0&%#4y?_R<$Qm7FueWr(-kN>J*8=8J z*=sXj_R+pr1*`&A0jq#jz$#!BunJfOtO8a6tAJI&D)2v4K)8$*(XKyeBah$zoBRJi z?_un7@EQ0JyauMh0q{8J0_(tPunOD)&fLw|r=SkjgR^V#9&~|q;FG%;^T81K;!eii z0MCJ4;9;;9e0K-x1#f{_@CdkaJ7X8YJK%NjJU9XdK@TVb4juyQK^OQHUsU`IE`X20 zY48#Vz|-IW=m%Y3B{+8*V;_K1;4pX)oLSA-M?e7yCc!gc0*rtjum)UN#n=zvD{vOP z2VMo!U>Dc`ep|`dm*7pX6%@gb_)_Ita2Z?x=fOGfGI$YGzz*;LSPg!`mo(pi^Wc4O z8k_=8f*wF)`1|#LA;am?Nx--9Y7&?;8N7(|871zhAdn`Z!~;1U_wZRpnOzZ!iil){ zA`{)Q6HPiu@l~z+Xvt)VwFsj0cn7j{Ybo6B67TEdaovk}b_4I|#wHc`Gp5v()DaGZ zP>HF{-Kiy >BNJKZDu_KuGp8lOCfe_81)GqqWT zbl61r$T@M;R7a^=o=7|`U6Yzx_5xH$Je~ed^<~13|UQpw$6EZxeBQhm^vhG0+qb6jm zB|^LCL}iBW8&2Xn>+9`hY;L>h=3J|@?RV65jBvRweF#~ui@^2eUNzDh?qKUb)~<-`Qd8kw#lq+`6ptm3;>7io zIWDvjo?9sKZdX>4T6a&opjYL^rE6=t@^bV0im#mG(rqheM98`CKko8{oY;l*5y?Eg z<4()L+k<2LdignGRlv8e(-ZUaf%Qd9PXDei=H>46#*TX}f6S-f;7mJzCdKT+Xg;EA z{$xt4V{W!d=d#>nL?v>Qk(N=2laCUu(K2q&S;lZjv8B=|-EA*U+qf6YMmd+lC`ac- zk8YNg-lorUXQtM2LzByxo8@n%PSPSY>ABY^JR)LQQ+j3*0|*z)twoJMk#d+s_2Mkc zHw>C3eq6?~I4Ycau^p;Vznjx}v%r+lq%8EEI67b6)>^V#X)NR}vXNaZAqBI96ms$} zy2xG6P%l_pN3~XZ;c6(g&Q(V%7Nz7?)mCepD_`1L>RX}%=`LtC6`=5J*l}bOm1VkM z9!R8~DHgCkPtG632nM1P{(hn13^C%b5?`<^x0OcYN||2GM$Rit4;K%$I)luU!ORs#^Fxhjwp z;5F^-Qp%Sim}~cVlQCNLMGd7z45>qmw+s-D!Z%PyB!zbBS@_c#6_)eH_f=>R<~m2y zRL~6Hb2Aceh|olwF``cdwHlS8`V)za;>FCi=dx6XR^Z5$srW{0VQ7pIdd*6k-b1n^ zf}H5LFy0JNB(M3Zf+*e#s#-*d7#%zaBrCOYoOT!ZqkJvpjV)|B5)94^d5O8*dJNKt zx|~k*1yl%iwdceIOdIL=54&ztadJ`A5WobgN%t|S?68Le>=CbO)!+l=0cuV|>KfXA z(u?bSk8D)n6bEI^gMC4J5sLCD+s=-%{U%W$j>isQ3TDNWR!$uONkbkNf%k0ENKBUI zekrYvl15ZGqrF-^=Ead7zPW#3kdLcyM7gMXEO5#tKJ5GaAmxbAmPqO;=~CYr?f>7# zUi}iF{Xe$C=DLji{AsWq^n#yo|9kLtmiHp%x0hAGDqt0`3RnfK0#*U5fK|XMU=^?m YSOxw|1(w|}V1M_AhwO*QoA~|VFLnG9u>b%7 diff --git a/actions/.challenges.js.swp b/actions/.challenges.js.swp deleted file mode 100644 index 66275bc8f4dfa0aaac9cd1e94b8ec779e0fa94a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196608 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 Date: Fri, 14 Oct 2016 16:14:07 -0700 Subject: [PATCH 11/11] fix cache for get endpoints --- actions/admins.js | 1 + actions/copilots.js | 1 + actions/reviewers.js | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/actions/admins.js b/actions/admins.js index 842ebeae9..3348a70cd 100755 --- a/actions/admins.js +++ b/actions/admins.js @@ -73,6 +73,7 @@ exports.admins = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', + cacheEnabled: false, transaction: 'read', // this action is read-only databases: ['tcs_catalog'], run: function (api, connection, next) { diff --git a/actions/copilots.js b/actions/copilots.js index a0fa9a4cf..2dc0b68dd 100755 --- a/actions/copilots.js +++ b/actions/copilots.js @@ -61,6 +61,7 @@ exports.copilots = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', + cacheEnabled: false, transaction: 'read', // this action is read-only databases: ['tcs_catalog'], run: function (api, connection, next) { diff --git a/actions/reviewers.js b/actions/reviewers.js index 510ab4ea0..2f2891f88 100755 --- a/actions/reviewers.js +++ b/actions/reviewers.js @@ -140,6 +140,7 @@ exports.reviewers = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', + cacheEnabled: false, transaction: 'read', // this action is read-only databases: ['tcs_catalog'], run: function (api, connection, next) { @@ -326,4 +327,4 @@ exports.removeReviewer = { api.helper.handleNoConnection(api, connection, next); } } -}; \ No newline at end of file +};