diff --git a/actions/srmChallenges.js b/actions/srmChallenges.js index ca12fb11a..b850b5e0c 100644 --- a/actions/srmChallenges.js +++ b/actions/srmChallenges.js @@ -1,7 +1,7 @@ /* * Copyright (C) 2013-2014 TopCoder Inc., All Rights Reserved. * - * @version 1.8 + * @version 1.9 * @author Sky_, freegod, panoptimum, Ghost_141 * changes in 1.1: * - implement srm API @@ -26,6 +26,8 @@ * - Update search srm challenges api. Add challengeName filter. * Changes in 1.8: * - Implement get srm practice problems api. + * Changes in 1.9: + * - Implement Rounds For Problem API */ /*jslint node: true, nomen: true */ "use strict"; @@ -2137,3 +2139,112 @@ exports.getPracticeProblems = { } } }; + +/** + * getSrmRoundsForProblem implements the rounds for problem api. + * + * @param {Object} api - The api object. + * @param {Object} connection - The connection object. + * @param {Function} next - The callback function. + */ +function getSrmRoundsForProblem(api, connection, next) { + // control flow designators + var problemId = 'problemId', + checkProblem = 'checkProblem', + // shortcuts + helper = api.helper, + dbConnectionMap = connection.dbConnectionMap, + dataAccess = _.bind(api.dataAccess.executeQuery, api.dataAccess); + return async.waterfall( + [ + function (cb) { + var id = parseInt(connection.params.problemId, 10), + error = helper.checkIdParameter(id, problemId), + results = {}; + if (error) { + return cb(error); + } + results[problemId] = id; + return cb(null, results); + }, + function (results, cb) { + var id = results[problemId]; + dataAccess( + 'check_problem_exists', + {problem_id: id}, + dbConnectionMap, + function (error, result) { + if (error) { + return cb(error); + } + results[checkProblem] = result; + return cb(null, results); + } + ); + }, + function (results, cb) { + if (results[checkProblem][0].is_there) { + return cb(null, results); + } + return cb(new NotFoundError("The problem doesn't exist.")); + }, + function (results, cb) { + var id = results[problemId]; + return dataAccess( + 'get_rounds_for_problem', + {problem_id: id}, + dbConnectionMap, + function (error, result) { + if (error) { + return cb(error); + } + return cb( + null, + { + rounds: helper.transferDBResults2Response(result) + } + ); + } + ); + } + ], + function (error, results) { + if (error) { + helper.handleError(api, connection, error); + return next(connection, true); + } + connection.response = results; + return next(connection, true); + } + ); +} + +/** + * Rounds For Problem API + * + * This api returns the rounds that used the given problem (identified by problem id). + * This api will exclude the practice rounds. + * This api includes only finished rounds + * + * @since 1.9 + */ +exports.getSrmRoundsForProblem = { + name: "getSrmRoundsForProblem", + description: "SRM Rounds For Problem API", + inputs: { + required: ['problemId'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'read', // this action is read-only + databases: ["topcoder_dw"], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute getSrmRoundsForProblem", 'debug'); + return getSrmRoundsForProblem(api, connection, next); + } + return api.helper.handleNoConnection(api, connection, next); + } +}; diff --git a/apiary.apib b/apiary.apib index 98e181063..ac762cda9 100644 --- a/apiary.apib +++ b/apiary.apib @@ -2082,7 +2082,7 @@ Register a new user. "value":"503", "description":"Servers are up but overloaded. Try again later." } - + ## My Profile [/user/activation-email] ### My Profile [GET] + Request @@ -2299,7 +2299,7 @@ Register a new user. "name":"Service Unavailable", "value":"503", "description":"Servers are up but overloaded. Try again later." - } + } ## get User Identity Information [/user/identity] ### Search My Challenges [GET] @@ -2340,7 +2340,7 @@ Request "name":"Service Unavailable", "value":"503", "description":"Servers are up but overloaded. Try again later." - } + } ## Get User Algo Challenges [/user/{handle}/challenges/algo?pageIndex={pageIndex}&pageSize={pageSize}&sortOrder={sortOrder}&sortColumn={sortColumn}] ### Get User Algo Challenges [GET] @@ -6764,6 +6764,110 @@ Request "description":"Servers are up but overloaded. Try again later." } +## Get SRM Rounds For Problem [/data/srm/problems/:problemId/rounds] +### Get SRM Rounds For Problem [GET] + ++ Parameters + + problemId (required, number, `12348765`) ... The problemId to get the rounds for. + ++ Response 200 (application/json) + + { + "rounds": [ + { + "contestId": 3005, + "contestName": "Contest 3005", + "roundId": 6, + "roundName": "Past Srm Round", + "divisionDescription": "Unrated Division", + "levelDescription": "Level 2001" + }, + { + "contestId": 3006, + "contestName": "Contest 3006", + "roundId": 7, + "roundName": "Past Tournament Round", + "divisionDescription": "Division-I", + "levelDescription": "Level 2001" + }, + { + "contestId": 3007, + "contestName": "Contest 3007", + "roundId": 8, + "roundName": "Past Long Round", + "divisionDescription": "Division-II", + "levelDescription": "Level 2001" + }, + { + "contestId": 3008, + "contestName": "Contest 3008", + "roundId": 9, + "roundName": "Past Event Round", + "divisionDescription": "No Division Applicable", + "levelDescription": "Level 2001" + } + ] + } + ++ Response 400 (application/json) + + { + "error": { + "name": "Bad Request", + "value": 400, + "description": "The request was invalid. An accompanying message will explain why.", + "details": "problemId should be number." + } + } + ++ Response 400 (application/json) + + { + "error": { + "name": "Bad Request", + "value": 400, + "description": "The request was invalid. An accompanying message will explain why.", + "details": "problemId should be positive." + } + } + ++ Response 400 (application/json) + + { + "error": { + "name": "Bad Request", + "value": 400, + "description": "The request was invalid. An accompanying message will explain why.", + "details": "problemId should be less or equal to 2147483647." + } + } + ++ Response 404 (application/json) + + { + "error": { + "name": "Not Found", + "value": 404, + "description": "The URI requested is invalid or the requested resource does not exist.", + "details": "The problem doesn't 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." + } ## View Algorithm SRM Challenges [/data/srm/challenges?listType={listType}&pageSize={pageSize}&pageIndex={pageIndex}&sortColumn={sortColumn}&sortOrder={sortOrder}] ### View Algorithm SRM Challenges [GET] @@ -7281,7 +7385,7 @@ Request "value":"503", "description":"Servers are up but overloaded. Try again later." } - + ## View Active Data Science Challenges [/dataScience/challenges/active?submissionEndFrom={submissionEndFrom}&submissionEndTo={submissionEndTo}] ### View Active Data Science Challenges [GET] diff --git a/docs/TC API - Rounds For Problem API 1.0.doc b/docs/TC API - Rounds For Problem API 1.0.doc new file mode 100644 index 000000000..447d2d256 Binary files /dev/null and b/docs/TC API - Rounds For Problem API 1.0.doc differ diff --git a/package.json b/package.json index 91b1bf86d..a4108c194 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "bigdecimal": "0.6.x", "bignum": "0.6.x", "java": "0.3.x", - "informix-wrapper": "git://github.com/cloudspokes/informix-wrapper.git#dee39dbda53f91081825f829325ed43566059d3d", + "informix-wrapper": "git://github.com/cloudspokes/informix-wrapper.git#46d1c91c3a8e164f888e88627b8da712e9ceb855", "forums-wrapper": "git://github.com/cloudspokes/forums-wrapper.git#12b57be495c2e10431173522bc9eff60e0575959", "asn1": "*", "crypto": "0.0.x", diff --git a/queries/check_problem_exists b/queries/check_problem_exists new file mode 100644 index 000000000..f146b4ad5 --- /dev/null +++ b/queries/check_problem_exists @@ -0,0 +1 @@ +SELECT count(*) AS is_there FROM problem WHERE problem_id = @problem_id@; diff --git a/queries/check_problem_exists.json b/queries/check_problem_exists.json new file mode 100644 index 000000000..3683ac172 --- /dev/null +++ b/queries/check_problem_exists.json @@ -0,0 +1,5 @@ +{ + "name" : "check_problem_exists", + "db" : "topcoder_dw", + "sqlfile" : "check_problem_exists" +} diff --git a/queries/get_rounds_for_problem b/queries/get_rounds_for_problem new file mode 100644 index 000000000..fd0002306 --- /dev/null +++ b/queries/get_rounds_for_problem @@ -0,0 +1,16 @@ +SELECT + r.round_id, + r.name AS round_name, + r.contest_id, + (SELECT name FROM contest c WHERE c.contest_id = r.contest_id) AS contest_name, + (SELECT division_desc FROM division_lu d WHERE p.division_id = d.division_id) AS division_description, + p.level_desc AS level_description +FROM + problem p, + round r +WHERE + p.problem_id = @problem_id@ AND + p.round_id = r.round_id AND + r.round_type_id IN (1,2,10,20) AND + r.status = 'P' +ORDER BY r.round_id ASC; diff --git a/queries/get_rounds_for_problem.json b/queries/get_rounds_for_problem.json new file mode 100644 index 000000000..f52cbb3f5 --- /dev/null +++ b/queries/get_rounds_for_problem.json @@ -0,0 +1,5 @@ +{ + "name" : "get_rounds_for_problem", + "db" : "topcoder_dw", + "sqlfile" : "get_rounds_for_problem" +} diff --git a/routes.js b/routes.js index 6805c6f5d..3eb2a586a 100755 --- a/routes.js +++ b/routes.js @@ -1,7 +1,7 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.64 + * @version 1.65 * @author vangavroche, Sky_, muzehyun, kurtrips, Ghost_141, ecnu_haozi, hesibo, LazyChild, isv, flytoj2ee, * @author panoptimum, bugbuka, Easyhard, TCASSEMBLER * @@ -148,6 +148,8 @@ * - Add route for get user marathon matches api. * Changes in 1.64: * - Add route for get user algorithm challenges api. + * Changes in 1.65: + * - Added route for Rounds For Problem API */ /*jslint node:true, nomen: true */ "use strict"; @@ -293,6 +295,7 @@ exports.routes = { { path: "/:apiVersion/data/srm/roundAccess", action: "loadRoundAccess"}, { path: "/:apiVersion/data/srm/schedule", action: "getSRMSchedule"}, { path: "/:apiVersion/data/srm/practice/problems", action: "getPracticeProblems" }, + { path: "/:apiVersion/data/srm/problems/:problemId/rounds", action: "getSrmRoundsForProblem" }, { path: "/:apiVersion/data/marathon/challenges/:roundId/regInfo", action: "getMarathonChallengeRegInfo" }, { path: "/:apiVersion/data/marathon/challenges/:id", action: "getMarathonChallenge" }, diff --git a/test/sqls/srmRoundsForProblem/topcoder_dw__clean b/test/sqls/srmRoundsForProblem/topcoder_dw__clean new file mode 100644 index 000000000..c39f359be --- /dev/null +++ b/test/sqls/srmRoundsForProblem/topcoder_dw__clean @@ -0,0 +1,79 @@ + +DELETE FROM problem WHERE problem_id = 2001; +DELETE FROM problem WHERE problem_id = 2002; +DELETE FROM problem WHERE problem_id = 2001; +DELETE FROM problem WHERE problem_id = 2001; +DELETE FROM problem WHERE problem_id = 2001; +DELETE FROM problem WHERE problem_id = 2001; +DELETE FROM problem WHERE problem_id = 2001; +DELETE FROM problem WHERE problem_id = 2001; +DELETE FROM problem WHERE problem_id = 2001; +DELETE FROM problem WHERE problem_id = 2001; +DELETE FROM problem WHERE problem_id = 2001; + +DELETE FROM room WHERE round_id = 1; +DELETE FROM round WHERE round_id = 1; +DELETE FROM contest WHERE contest_id = 3000; +DELETE FROM room WHERE round_id = 2; +DELETE FROM round WHERE round_id = 2; +DELETE FROM contest WHERE contest_id = 3001; +DELETE FROM room WHERE round_id = 3; +DELETE FROM round WHERE round_id = 3; +DELETE FROM contest WHERE contest_id = 3002; +DELETE FROM room WHERE round_id = 4; +DELETE FROM round WHERE round_id = 4; +DELETE FROM contest WHERE contest_id = 3003; +DELETE FROM room WHERE round_id = 5; +DELETE FROM round WHERE round_id = 5; +DELETE FROM contest WHERE contest_id = 3004; +DELETE FROM room WHERE round_id = 6; +DELETE FROM round WHERE round_id = 6; +DELETE FROM contest WHERE contest_id = 3005; +DELETE FROM room WHERE round_id = 7; +DELETE FROM round WHERE round_id = 7; +DELETE FROM contest WHERE contest_id = 3006; +DELETE FROM room WHERE round_id = 8; +DELETE FROM round WHERE round_id = 8; +DELETE FROM contest WHERE contest_id = 3007; +DELETE FROM room WHERE round_id = 9; +DELETE FROM round WHERE round_id = 9; +DELETE FROM contest WHERE contest_id = 3008; +DELETE FROM room WHERE round_id = 10; +DELETE FROM round WHERE round_id = 10; +DELETE FROM contest WHERE contest_id = 3009; +DELETE FROM room WHERE round_id = 11; +DELETE FROM round WHERE round_id = 11; +DELETE FROM contest WHERE contest_id = 3010; + +DELETE FROM round_type_lu WHERE round_type_id = 1; +DELETE FROM round_type_lu WHERE round_type_id = 2; +DELETE FROM round_type_lu WHERE round_type_id = 3; +DELETE FROM round_type_lu WHERE round_type_id = 4; +DELETE FROM round_type_lu WHERE round_type_id = 10; +DELETE FROM round_type_lu WHERE round_type_id = 6; +DELETE FROM round_type_lu WHERE round_type_id = 11; +DELETE FROM round_type_lu WHERE round_type_id = 7; +DELETE FROM round_type_lu WHERE round_type_id = 8; +DELETE FROM round_type_lu WHERE round_type_id = 9; +DELETE FROM round_type_lu WHERE round_type_id = 12; +DELETE FROM round_type_lu WHERE round_type_id = 13; +DELETE FROM round_type_lu WHERE round_type_id = 14; +DELETE FROM round_type_lu WHERE round_type_id = 15; +DELETE FROM round_type_lu WHERE round_type_id = 16; +DELETE FROM round_type_lu WHERE round_type_id = 17; +DELETE FROM round_type_lu WHERE round_type_id = 18; +DELETE FROM round_type_lu WHERE round_type_id = 19; +DELETE FROM round_type_lu WHERE round_type_id = 20; +DELETE FROM round_type_lu WHERE round_type_id = 21; +DELETE FROM round_type_lu WHERE round_type_id = 22; +DELETE FROM round_type_lu WHERE round_type_id = 23; +DELETE FROM round_type_lu WHERE round_type_id = 27; + +DELETE FROM algo_rating_type_lu WHERE algo_rating_type_id = 1; +DELETE FROM algo_rating_type_lu WHERE algo_rating_type_id = 2; +DELETE FROM algo_rating_type_lu WHERE algo_rating_type_id = 3; + +DELETE FROM division_lu WHERE division_id = -1; +DELETE FROM division_lu WHERE division_id = 0; +DELETE FROM division_lu WHERE division_id = 1; +DELETE FROM division_lu WHERE division_id = 2; diff --git a/test/sqls/srmRoundsForProblem/topcoder_dw__insert b/test/sqls/srmRoundsForProblem/topcoder_dw__insert new file mode 100644 index 000000000..34b950a4d --- /dev/null +++ b/test/sqls/srmRoundsForProblem/topcoder_dw__insert @@ -0,0 +1,81 @@ + +INSERT INTO division_lu (division_id, division_desc) VALUES (-1, 'No Division Applicable'); +INSERT INTO division_lu (division_id, division_desc) VALUES (0, 'Unrated Division'); +INSERT INTO division_lu (division_id, division_desc) VALUES (1, 'Division-I'); +INSERT INTO division_lu (division_id, division_desc) VALUES (2, 'Division-II'); + +INSERT INTO algo_rating_type_lu (algo_rating_type_id, algo_rating_type_desc) VALUES (1, 'TopCoder Rating'); +INSERT INTO algo_rating_type_lu (algo_rating_type_id, algo_rating_type_desc) VALUES (2, 'TopCoder High School Rating'); +INSERT INTO algo_rating_type_lu (algo_rating_type_id, algo_rating_type_desc) VALUES (3, 'Marathon Match Rating'); + + +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (1, 'Single Round Match', 1); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (2, 'Tournament Round', 1); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (3, 'Practice Round', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (4, 'Moderated Chat Session', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (10, 'Long Round', 1); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (6, 'Screening Tool Problem Sets', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (11, 'Weakest Link Round', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (7, 'Team Single Round Match', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (8, 'Team Tournament Round', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (9, 'Team Practice Round', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (12, 'Private Label Tournament', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (13, 'Marathon Match', 3); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (14, 'Marathon Match Practice Round', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (15, 'Intel Marathon Match', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (16, 'Intel Marathon Match Practice Round', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (17, 'HS Single Round Match', 2); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (18, 'HS Tournament Round', 2); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (19, 'Marathon Match Tournament Round', 3); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (20, 'Intro Event Round', 1); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (21, 'Education Round', 1); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (22, 'AMD Marathon Match', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (23, 'AMD Marathon Match Practice', NULL); +INSERT INTO round_type_lu (round_type_id, round_type_desc, algo_rating_type_id) VALUES (27, 'Marathon Match QA', NULL); + +INSERT INTO contest (contest_id, name) VALUES (3000, 'Contest 3000'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (1, 3000, 1, 'Active Srm Round', 'A'); +INSERT INTO contest (contest_id, name) VALUES (3001, 'Contest 3001'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (2, 3001, 1, 'Active Srm Round 2', 'A'); +INSERT INTO contest (contest_id, name) VALUES (3002, 'Contest 3002'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (3, 3002, 2, 'Active Tournament Round', 'A'); +INSERT INTO contest (contest_id, name) VALUES (3003, 'Contest 3003'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (4, 3003, 10, 'Active Long Round', 'A'); +INSERT INTO contest (contest_id, name) VALUES (3004, 'Contest 3004'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (5, 3004, 20, 'Active Event Round', 'A'); +INSERT INTO contest (contest_id, name) VALUES (3005, 'Contest 3005'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (6, 3005, 1, 'Past Srm Round', 'P'); +INSERT INTO contest (contest_id, name) VALUES (3006, 'Contest 3006'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (7, 3006, 2, 'Past Tournament Round', 'P'); +INSERT INTO contest (contest_id, name) VALUES (3007, 'Contest 3007'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (8, 3007, 10, 'Past Long Round', 'P'); +INSERT INTO contest (contest_id, name) VALUES (3008, 'Contest 3008'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (9, 3008, 20, 'Past Event Round', 'P'); +INSERT INTO contest (contest_id, name) VALUES (3009, 'Contest 3009'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (10, 3009, 3, 'Past Practice Round', 'P'); +INSERT INTO contest (contest_id, name) VALUES (3010, 'Contest 3010'); +INSERT INTO round (round_id, contest_id, round_type_id, name, status) VALUES (11, 3010, 9, 'Past Tournament Practice Round', 'P'); + +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4001, 1, 'Room 4001', -1, 'No Division Applicable'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4002, 2, 'Room 4002', 0, 'Unrated Division'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4003, 3, 'Room 4003', 1, 'Division-I'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4004, 4, 'Room 4004', 2, 'Division-II'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4005, 5, 'Room 4005', -1, 'No Division Applicable'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4006, 6, 'Room 4006', 0, 'Unrated Division'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4007, 7, 'Room 4007', 1, 'Division-I'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4008, 8, 'Room 4008', 1, 'Division-I'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4009, 9, 'Room 4009', -1, 'No Division Applicable'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4010, 10, 'Room 4010', 0, 'Unrated Division'); +INSERT INTO room (room_id, round_id, name, division_id, division_desc) VALUES (4011, 11, 'Room 4011', 1, 'Division-I'); + +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 1, 'Level 2001', 18, 'Method 2001', 'Class 2001', -1); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2002, 2, 'Level 2002', 18, 'Method 2002', 'Class 2002', 0); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 3, 'Level 2001', 18, 'Method 2001', 'Class 2001', 1); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 4, 'Level 2001', 18, 'Method 2001', 'Class 2001', 2); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 5, 'Level 2001', 18, 'Method 2001', 'Class 2001', -1); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 6, 'Level 2001', 18, 'Method 2001', 'Class 2001', 0); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 7, 'Level 2001', 18, 'Method 2001', 'Class 2001', 1); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 8, 'Level 2001', 18, 'Method 2001', 'Class 2001', 2); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 9, 'Level 2001', 18, 'Method 2001', 'Class 2001', -1); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 10, 'Level 2001', 18, 'Method 2001', 'Class 2001', 0); +INSERT INTO problem (problem_id, round_id, level_desc, result_type_id, method_name, class_name, division_id) VALUES (2001, 11, 'Level 2001', 18, 'Method 2001', 'Class 2001', 1); diff --git a/test/test.srmRoundsForProblem.js b/test/test.srmRoundsForProblem.js new file mode 100644 index 000000000..ac478e3f8 --- /dev/null +++ b/test/test.srmRoundsForProblem.js @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2014 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author panoptimum + */ +/*global describe, it, before, beforeEach, after, afterEach*/ +/*jslint node: true, nomen: true*/ +"use strict"; + +/** + * Module dependencies. + */ +var _ = require('underscore'), + async = require('async'), + request = require('supertest'), + chai = require('chai'), + testHelper = require('./helpers/testHelper'), + util = require('util'); + + +chai.Assertion.includeStack = true; +var assert = chai.assert; +/** + * Constants + */ +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080', + SQL_DIR = __dirname + '/sqls/srmRoundsForProblem/', + ROUTE = '/v2/data/srm/problems/%s/rounds', + DB = ['topcoder_dw']; + +/* This function returns a function that takes a callback and runs a sql file + * @param {String} suffix "clean" or "insert" + * @param {String} db the database name, e.g. "common_oltp", "informixoltp" + * @return {Function} function that takes a callback and runs a sql file + */ +function runSqlFile(suffix, db) { + return _.bind( + testHelper.runSqlFile, + testHelper, + util.format("%s%s__%s", SQL_DIR, db, suffix), + db + ); +} + +/** + * Runs the sql file of a given suffix for each db in a list. + * @param {String} suffix the suffix, e.g. clean + * @param {Array} dbs list of database names, e.g. informixoltp + * @param {Function} done callback function + */ +function runSqlFiles(suffix, dbs, done) { + async.series( + _.map( + dbs, + function (db) { + return runSqlFile(suffix, db); + } + ), + done + ); +} + +/** + * Create and return GET request + * @param {Object} data the data to be queried + * @return {Object} request object + */ +function createGetRequest(data) { + var result = request(API_ENDPOINT) + .get(data.route) + .query(_.omit(data.request, 'problemId')) + .set('Accept', 'application/json'); + return result; +} + +/** + * Send request and check if response conforms to API contract + * @param {Object} testData configuration object + */ +function assertResponse(testData) { + var status = testData.status, + responseData = testData.response, + createRequest = createGetRequest; + return function (done) { + createRequest(testData) + .expect(status) + .expect('Content-Type', /json/) + .end( + function (error, response) { + var result; + if (error) { + return done(error); + } + if (!response) { + return done(new Error("Server unresponsive.")); + } + if (status === 200) { + result = testHelper.getTrimmedData(response.res.text); + assert.deepEqual(result, responseData, "Responses must match."); + return done(); + } + + result = testHelper.getTrimmedData(response.res.text); + assert.deepEqual(result, responseData, + 'response does not conform to expected value'); + return done(error); + + } + ); + }; +} + +/** + * Assert requests to the Rounds For Problem API + * + * @param {Object} request - an object representing the request to be send + * @param {Object} response - an object representing the expected response + * @param {Integer} status - an int representing the expected status + * + * @return {Function} a function that takes a callback and does the assertion + */ +function rfp(request, response, status) { + return assertResponse({ + request: request, + response: response, + status: status, + route: util.format(ROUTE, request.problemId) + }); +} + +describe('SRM Rounds For Problem', function () { + this.timeout(60000); // Wait a minute, remote db might be slow. + var clearDb = _.partial(runSqlFiles, "clean", DB), + fillDb = _.partial(runSqlFiles, "insert", DB); + before( + function (done) { + async.series( + [ + clearDb, + fillDb + ], + done + ); + } + ); + after(clearDb); + + describe('Invalid Requests', function () { + it( + "Invalid problemId - NaN", + rfp( + { + problemId: "foobar" + }, + { + "error": { + "name": "Bad Request", + "value": 400, + "description": "The request was invalid. An accompanying message will explain why.", + "details": "problemId should be number." + } + }, + 400 + ) + ); + + it( + "Invalid problemId - too small", + rfp( + { + problemId: "0" + }, + { + "error": { + "name": "Bad Request", + "value": 400, + "description": "The request was invalid. An accompanying message will explain why.", + "details": "problemId should be positive." + } + }, + 400 + ) + ); + + it( + "Invalid problemId - too small", + rfp( + { + problemId: "2147483648" + }, + { + "error": { + "name": "Bad Request", + "value": 400, + "description": "The request was invalid. An accompanying message will explain why.", + "details": "problemId should be less or equal to 2147483647." + } + }, + 400 + ) + ); + + + + it( + "Invalid problemId - problem doesn't exist", + rfp( + { + problemId: 2003 + }, + { + "error": { + "name": "Not Found", + "value": 404, + "description": "The URI requested is invalid or the requested resource does not exist.", + "details": "The problem doesn't exist." + } + }, + 404 + ) + ); + + }); + + describe('Valid Requests', function () { + + // The following round refers to problem with problem_id 2002: + // round_id: 3001 - active srm round - omit + it( + "Round exists, but is not finished, empty result.", + rfp( + { + problemId: 2002 + }, + { + rounds: [] + }, + 200 + ) + ); + + // The following rounds refer to problem with problem_id 2001: + // round_id: 3000 - active srm round - omit + // round_id: 3002 - active tournament round - omit + // round_id: 3003 - active long round - omit + // round_id: 3004 - active info event round - omit + // round_id: 3005 - past srm round - show + // round_id: 3006 - past tournament round - show + // round_id: 3007 - past long round - show + // round_id: 3008 - past info event round - show + // round_id: 3009 - past practice round - omit + // round_id: 3010 - past tournament practice round - omit + it( + "Don't report non-finished rounds, test rounds.", + rfp( + { + problemId: 2001 + }, + { + rounds: [ + { + "contestId": 3005, + "contestName": "Contest 3005", + "roundId": 6, + "roundName": "Past Srm Round", + "divisionDescription": "Unrated Division", + "levelDescription": "Level 2001" + }, + { + "contestId": 3006, + "contestName": "Contest 3006", + "roundId": 7, + "roundName": "Past Tournament Round", + "divisionDescription": "Division-I", + "levelDescription": "Level 2001" + }, + { + "contestId": 3007, + "contestName": "Contest 3007", + "roundId": 8, + "roundName": "Past Long Round", + "divisionDescription": "Division-II", + "levelDescription": "Level 2001" + }, + { + "contestId": 3008, + "contestName": "Contest 3008", + "roundId": 9, + "roundName": "Past Event Round", + "divisionDescription": "No Division Applicable", + "levelDescription": "Level 2001" + } + ] + }, + 200 + ) + ); + }); +});