diff --git a/actions/marathonChallenges.js b/actions/marathonChallenges.js index 6569e8ae7..665ab3614 100644 --- a/actions/marathonChallenges.js +++ b/actions/marathonChallenges.js @@ -1,14 +1,17 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.3 - * @author Sky_, TCSASSEMBLER, freegod, Ghost_141 + * @version 1.4 + * @author Sky_, TCSASSEMBLER, freegod, Ghost_141, hesibo * changes in 1.1: * - implement marathon API * changes in 1.2: * - Use empty result set instead of 404 error in get marathon challenges API. * Changes in 1.3: * - Implement the register marathon match challenge API. + * changes in 1.4: + * - Implement the get marathon match challenge register info API + * - refactor register marathon match challenge API */ "use strict"; var async = require('async'); @@ -104,8 +107,8 @@ function setDateToParams(helper, sqlParams, dateInterval, inputCodePrefix) { } /** -* The API for searching Marathon challenges -*/ + * The API for searching Marathon challenges + */ exports.searchMarathonChallenges = { name: "searchMarathonChallenges", description: "searchMarathonChallenges", @@ -294,7 +297,7 @@ exports.searchMarathonChallenges = { /** * Compute progressResources field for challenge details - * + * * @param {Array} submissions - the submissions. Result of detail_progress_XXX query. * @param {Array} registrants - the registrants. Result of detail_progress_XXX_registrants query. * @param {Array} competitors - the competitors. Result of detail_progress_competitors query. @@ -398,8 +401,8 @@ function computeProgressResources(submissions, registrants, competitors, interva } /** -* The API for getting Marathon challenge -*/ + * The API for getting Marathon challenge + */ exports.getMarathonChallenge = { name: "getMarathonChallenge", description: "getMarathonChallenge", @@ -515,35 +518,36 @@ exports.getMarathonChallenge = { }); } }; - /** - * Register the marathon match challenge. - * @param {Object} api - the api object. - * @param {Object} connection - the connection object. - * @param {Function} next - the callback function. - * @since 1.3 + * perform checking before register marathon or view register info + * + * @param {Object} api - the api object + * @param {Boolean} isRegister - it is register marathon or not + * @param {Object} connection - the connection object + * @param {Object} sqlParams - the sql parameters object + * @param {Function} callback - the callback function + * + * @since 1.4 */ -function registerMarathonMatchChallenge(api, connection, next) { - var helper = api.helper, dbConnectionMap = connection.dbConnectionMap, roundId, sqlParams, isHighSchool, +function preRegisterMarathonCheck(api, isRegister, connection, sqlParams, callback) { + var dbConnectionMap = connection.dbConnectionMap, + roundId = Number(decodeURI(connection.params.roundId).trim()), execQuery = function (name) { return function (cb) { api.dataAccess.executeQuery(name, sqlParams, dbConnectionMap, cb); }; - }, caller = connection.caller; - roundId = Number(decodeURI(connection.params.roundId).trim()); + }; async.waterfall([ function (cb) { - var error = helper.checkPositiveInteger(roundId, 'roundId') || - helper.checkMaxInt(roundId, 'roundId') || - helper.checkMember(connection, 'Authorization information needed or incorrect.'); + var error = api.helper.checkPositiveInteger(roundId, 'roundId') || + api.helper.checkMaxInt(roundId, 'roundId') || + api.helper.checkMember(connection, 'Authorization information needed or incorrect.'); if (error) { cb(error); return; } - sqlParams = { - round_id: roundId, - user_id: caller.userId - }; + sqlParams.round_id = roundId; + sqlParams.user_id = connection.caller.userId; // check async.parallel({ checkResult: execQuery('check_marathon_challenge_register'), @@ -588,7 +592,7 @@ function registerMarathonMatchChallenge(api, connection, next) { } } // If the caller has already reigstered for this challenge. - if (checkResult.is_round_registered) { + if (isRegister && checkResult.is_round_registered) { cb(new BadRequestError('You already registered for this challenge.')); return; } @@ -619,10 +623,39 @@ function registerMarathonMatchChallenge(api, connection, next) { cb(new BadRequestError('There are no more spots available for the round.')); return; } + cb(null, checkResult); + } + ], function (err, checkResult) { + if (isRegister) { + callback(err, checkResult); + } else { + callback(err); + } + }); +} +/** + * Register the marathon match challenge. + * @param {Object} api - the api object. + * @param {Object} connection - the connection object. + * @param {Function} next - the callback function. + * @since 1.3 + */ +function registerMarathonMatchChallenge(api, connection, next) { + var sqlParams = {}, + execQuery = function (name) { + return function (cb) { + api.dataAccess.executeQuery(name, sqlParams, connection.dbConnectionMap, cb); + }; + }; + async.waterfall([ + function (cb) { + preRegisterMarathonCheck(api, true, connection, sqlParams, cb); + }, + function (checkResult, cb) { _.extend(sqlParams, { eligible: 1, - userId: caller.userId, + userId: connection.caller.userId, attended: 'N' }); async.parallel({ @@ -630,7 +663,7 @@ function registerMarathonMatchChallenge(api, connection, next) { roundTerms: execQuery('insert_round_terms_acceptance'), algoRating: function (cbx) { if (!checkResult.is_rated) { - api.dataAccess.executeQuery('add_algo_rating', sqlParams, dbConnectionMap, cbx); + api.dataAccess.executeQuery('add_algo_rating', sqlParams, connection.dbConnectionMap, cbx); return; } cbx(); @@ -642,7 +675,7 @@ function registerMarathonMatchChallenge(api, connection, next) { } ], function (err) { if (err) { - helper.handleError(api, connection, err); + api.helper.handleError(api, connection, err); } else { connection.response = { success: true }; } @@ -677,3 +710,103 @@ exports.registerMarathonChallenge = { } } }; + +/** + * Get marathon match challenge register information. + * + * @param {Object} api - the api object. + * @param {Object} connection - the connection object. + * @param {Function} next - the callback function. + * + * @since 1.4 + */ +function getMarathonChallengeRegInfo(api, connection, next) { + var sqlParams = {}, result = {}, questionIdMapping = {}, index = 0; + async.waterfall([ + function (cb) { + preRegisterMarathonCheck(api, false, connection, sqlParams, cb); + }, + function (cb) { + api.dataAccess.executeQuery('get_marathon_round_term', sqlParams, connection.dbConnectionMap, cb); + }, + function (term, cb) { + if (term.length === 0) { + cb(new NotFoundError('Could not find specified round terms.')); + return; + } + result.term = { + contestName: term[0].contest_name, + roundName: term[0].round_name, + termsContent: term[0].terms_content || '' + }; + api.dataAccess.executeQuery('get_marathon_round_questions', sqlParams, connection.dbConnectionMap, cb); + }, + function (questions, cb) { + sqlParams.question_ids = []; + result.questions = _.map(questions, function (question) { + sqlParams.question_ids.push(question.question_id); + questionIdMapping[question.question_id] = index; + index = index + 1; + return { + id: question.question_id, + style: question.style, + type: question.type, + text: question.text, + answers: [] + }; + }); + if (!_.isEmpty(sqlParams.question_ids)) { + api.dataAccess.executeQuery('get_marathon_round_question_answers', sqlParams, connection.dbConnectionMap, cb); + } else { + cb(null, null); + } + }, + function (answers, cb) { + if (!_.isEmpty(sqlParams.question_ids)) { + answers.forEach(function (answer) { + result.questions[questionIdMapping[answer.question_id]].answers.push({ + id: answer.answer_id, + text: answer.text, + sortOrder: answer.sort_order || -1, + correct: answer.correct === 0 ? false : true + }); + }); + } + cb(); + } + ], function (err) { + if (err) { + api.helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); +} + +/** + * The API for get marathon match challenge register information. + * + * @since 1.4 + */ +exports.getMarathonChallengeRegInfo = { + name: 'getMarathonChallengeRegInfo', + description: 'get marathon match challenge register information', + inputs: { + required: ['roundId'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'read', // this action is read-only + databases: ['informixoltp', 'common_oltp'], + run: function (api, connection, next) { + if (!connection.dbConnectionMap) { + api.helper.handleNoConnection(api, connection, next); + } else { + api.log('Execute getMarathonChallengeRegInfo#run', 'debug'); + getMarathonChallengeRegInfo(api, connection, next); + } + } +}; diff --git a/apiary.apib b/apiary.apib index 7f84387e6..467462e65 100644 --- a/apiary.apib +++ b/apiary.apib @@ -4440,6 +4440,199 @@ Register a new user. "description":"Servers are up but overloaded. Try again later." } +## Get Marathon Match Challenge Reg Info [/data/marathon/challenges/{roundId}/regInfo] +### Get Marathon Match Challenge Reg Info [GET] + ++ Parameters + + roundId (required, number, `30000000`) ... The challenge round id. + ++ Request + + + Headers + + Authorization : Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + ++ Response 200 (application/json) + + { + "term": { + "contestName": "Marathon Match 90", + "roundName": "Round 2001", + "termsContent": "Marathon Match terms content" + }, + "questions": [ + { + "id": 1000, + "style": "Multiple Choice", + "type": "Eligible", + "text": "question 1000", + "answers": [ + { + "id": 1002, + "text": "answer text 1", + "sortOrder": 1, + "correct": true + }, + { + "id": 1001, + "text": "answer text 2", + "sortOrder": 2, + "correct": false + }, + { + "id": 1003, + "text": "answer text 3", + "sortOrder": 3, + "correct": false + } + ] + }, + { + "id": 1001, + "style": "Short Answer", + "type": "Personal", + "text": "question 1001", + "answers": [] + }, + { + "id": 1002, + "style": "Single Choice", + "type": "Eligible", + "text": "question 1002", + "answers": [ + { + "id": 1004, + "text": "answer text 4", + "sortOrder": -1, + "correct": false + }, + { + "id": 1005, + "text": "answer text 5", + "sortOrder": 1, + "correct": true + } + ] + } + ] + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"This message will explain why the request is invalid or cannot be served." + "details:":"You are not eligible to participate in this competition." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"This message will explain why the request is invalid or cannot be served." + "details:":"Round doesn't exist 30005520." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"This message will explain why the request is invalid or cannot be served." + "details":"In order to participate in this competition, you must register for Event 30005520. Registration is available: here. Please register at the provided URL first and then repeat registration at Marathon Match Active Contests page." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"This message will explain why the request is invalid or cannot be served." + "details":"Registration is not currently open." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"This message will explain why the request is invalid or cannot be served." + "details":"You are not eligible to participate in this competition. Please contact support@topcoder.com if you have any questions." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"This message will explain why the request is invalid or cannot be served." + "details":"Sorry, this round is by invitation only." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"This message will explain why the request is invalid or cannot be served." + "details":"Sorry, you can not register for this round, you must compete in the version of this round that you were invited to." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"This message will explain why the request is invalid or cannot be served." + "details":"There are no more spots available for the round." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"This message will explain why the request is invalid or cannot be served." + "details":"Authorization information needed or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"This message will explain why the request is invalid or cannot be served." + "details":"The user is forbidden to access this endpoint." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"This message will explain why the request is invalid or cannot be served." + "details":"Could not find specified round terms." + } + ++ 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." + } + + ## Register Marathon Match Challenge [/data/marathon/challenges/{roundId}/register] ### Register Marathon Match Challenge [POST] diff --git a/docs/Module Assembly - TopCoder NodeJS Get Marathon Match Challenge Reg Info API.doc b/docs/Module Assembly - TopCoder NodeJS Get Marathon Match Challenge Reg Info API.doc new file mode 100755 index 000000000..89139d80c Binary files /dev/null and b/docs/Module Assembly - TopCoder NodeJS Get Marathon Match Challenge Reg Info API.doc differ diff --git a/queries/get_marathon_round_question_answers b/queries/get_marathon_round_question_answers new file mode 100755 index 000000000..6c01e689a --- /dev/null +++ b/queries/get_marathon_round_question_answers @@ -0,0 +1,9 @@ +SELECT + answer_id, + question_id, + answer_text AS text, + sort_order, + NVL(correct, 0) AS correct +FROM answer +WHERE question_id IN (@question_ids@) +ORDER BY sort_order \ No newline at end of file diff --git a/queries/get_marathon_round_question_answers.json b/queries/get_marathon_round_question_answers.json new file mode 100755 index 000000000..9d37d1c0a --- /dev/null +++ b/queries/get_marathon_round_question_answers.json @@ -0,0 +1,5 @@ +{ + "name" : "get_marathon_round_question_answers", + "db" : "informixoltp", + "sqlfile" : "get_marathon_round_question_answers" +} \ No newline at end of file diff --git a/queries/get_marathon_round_questions b/queries/get_marathon_round_questions new file mode 100755 index 000000000..12119e5b6 --- /dev/null +++ b/queries/get_marathon_round_questions @@ -0,0 +1,15 @@ +SELECT + qu.question_id, + qs.question_style_desc AS style, + qt.question_type_desc AS type, + qu.question_text AS text +FROM + round_question rq, + question qu, + question_style qs, + question_type qt +WHERE rq.question_id = qu.question_id + AND rq.round_id = @round_id@ + AND qs.question_style_id = qu.question_style_id + AND qt.question_type_id = qu.question_type_id +ORDER BY qu.question_id \ No newline at end of file diff --git a/queries/get_marathon_round_questions.json b/queries/get_marathon_round_questions.json new file mode 100755 index 000000000..2c0309b39 --- /dev/null +++ b/queries/get_marathon_round_questions.json @@ -0,0 +1,5 @@ +{ + "name" : "get_marathon_round_questions", + "db" : "informixoltp", + "sqlfile" : "get_marathon_round_questions" +} \ No newline at end of file diff --git a/queries/get_marathon_round_term b/queries/get_marathon_round_term new file mode 100755 index 000000000..1cfbbc957 --- /dev/null +++ b/queries/get_marathon_round_term @@ -0,0 +1,11 @@ +SELECT + terms_content, + ct.name AS contest_name, + rd.name AS round_name +FROM + round_terms rt, + round rd, + contest ct +WHERE rt.round_id = @round_id@ + AND rt.round_id = rd.round_id + AND rd.contest_id = ct.contest_id \ No newline at end of file diff --git a/queries/get_marathon_round_term.json b/queries/get_marathon_round_term.json new file mode 100755 index 000000000..a79c793e9 --- /dev/null +++ b/queries/get_marathon_round_term.json @@ -0,0 +1,5 @@ +{ + "name" : "get_marathon_round_term", + "db" : "informixoltp", + "sqlfile" : "get_marathon_round_term" +} \ No newline at end of file diff --git a/routes.js b/routes.js index adaeac535..356a1e2f6 100755 --- a/routes.js +++ b/routes.js @@ -1,7 +1,7 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.29 + * @version 1.30 * @author vangavroche, Sky_, muzehyun, kurtrips, Ghost_141, ecnu_haozi, hesibo, LazyChild * Changes in 1.1: * - add routes for search challenges @@ -69,6 +69,8 @@ * - added route for Dosusign get recipient view url * changes in 1.29 * - added route for activate user api + * changes in 1.30 + * - added route for getting marathon match challenge register info api */ /* --------------------- @@ -179,6 +181,7 @@ exports.routes = { { path: "/:apiVersion/data/srm/challenges/:id", action: "getSRMChallenge" }, { path: "/:apiVersion/data/srm/challenges", action: "searchSRMChallenges" }, + { path: "/:apiVersion/data/marathon/challenges/:roundId/regInfo", action: "getMarathonChallengeRegInfo" }, { path: "/:apiVersion/data/marathon/challenges/:id", action: "getMarathonChallenge" }, { path: "/:apiVersion/data/marathon/challenges", action: "searchMarathonChallenges" }, { path: "/:apiVersion/data/marathon/statistics/tops", action: "getMarathonTops" }, diff --git a/test/sqls/marathonRegInfo/common_oltp__clean b/test/sqls/marathonRegInfo/common_oltp__clean new file mode 100755 index 000000000..f840911ef --- /dev/null +++ b/test/sqls/marathonRegInfo/common_oltp__clean @@ -0,0 +1,7 @@ +DELETE FROM user_address_xref WHERE user_id = 300003; +DELETE FROM address WHERE address_id = 2001; +DELETE FROM event_registration WHERE event_id = 2001; +DELETE FROM event WHERE event_id = 2001; +DELETE FROM user_role_xref WHERE login_id IN (300002, 300003); +DELETE FROM security_user WHERE login_id IN (300002, 300003); +DELETE FROM user WHERE user_id IN (300001, 300002, 300003); diff --git a/test/sqls/marathonRegInfo/common_oltp__insert_test_data b/test/sqls/marathonRegInfo/common_oltp__insert_test_data new file mode 100755 index 000000000..c3bbee2c0 --- /dev/null +++ b/test/sqls/marathonRegInfo/common_oltp__insert_test_data @@ -0,0 +1,26 @@ +INSERT INTO user(user_id, handle, status) VALUES(300001, 'testUser300001', 'A'); + +INSERT INTO user(user_id, handle, status) VALUES(300002, 'testUser300002', 'U'); +INSERT INTO security_user(login_id, user_id, password, create_user_id) +VALUES(300002, 'testUser300002', '4EjPjy6o+/C+dqNPnxIy9A==', 0); +INSERT INTO user_role_xref(user_role_id, login_id, role_id, create_user_id, security_status_id) +VALUES(2001, 300002, 1000, 132456, 1); + +INSERT INTO user(user_id, handle, status) VALUES(300003, 'testUser300003', 'A'); +INSERT INTO security_user(login_id, user_id, password, create_user_id) +VALUES(300003, 'testUser300003', '4EjPjy6o+/C+dqNPnxIy9A==', 0); +INSERT INTO user_role_xref(user_role_id, login_id, role_id, create_user_id, security_status_id) +VALUES(2002, 300003, 1000, 132456, 1); +INSERT INTO address(address_id, address_type_id, country_code, create_date, modify_date) +VALUES(2001, 2, 192, current, current); +INSERT INTO user_address_xref(user_id, address_id) VALUES(300003, 2001); + +INSERT INTO event(event_id, event_type_id, event_desc, modify_date) VALUES(2001, 1, 'Test Event 2001', current); +INSERT INTO event_registration(event_id, user_id, eligible_ind, notes, create_date, modify_date) +VALUES(2001, 124764, 0, 'test', current, current); +INSERT INTO event_registration(event_id, user_id, eligible_ind, notes, create_date, modify_date) +VALUES(2001, 124766, 1, 'test', current, current); +INSERT INTO event_registration(event_id, user_id, eligible_ind, notes, create_date, modify_date) +VALUES(2001, 300003, 1, 'test', current, current); +INSERT INTO event_registration(event_id, user_id, eligible_ind, notes, create_date, modify_date) +VALUES(2001, 132456, 1, 'test', current, current); diff --git a/test/sqls/marathonRegInfo/informixoltp__clean b/test/sqls/marathonRegInfo/informixoltp__clean new file mode 100755 index 000000000..98dd846cc --- /dev/null +++ b/test/sqls/marathonRegInfo/informixoltp__clean @@ -0,0 +1,19 @@ +DELETE FROM answer WHERE question_id IN (1000, 1001, 1002); +DELETE FROM round_question WHERE round_id = 2001; +DELETE FROM question WHERE question_id IN (1000, 1001, 1002); + +DELETE FROM round_terms WHERE round_id IN (2001, 2006); + +DELETE FROM round_registration WHERE round_id >= 2001; +DELETE FROM round_terms_acceptance WHERE round_id >= 2001; +DELETE FROM algo_rating WHERE coder_id IN (132456); +DELETE FROM long_comp_result WHERE round_id >= 2001; + +DELETE FROM round_event WHERE round_id IN (2001, 2006, 2007); +DELETE FROM round_segment WHERE round_id IN (2001, 2002, 2003, 2004, 2005, 2006, 2007); +DELETE FROM round_component WHERE round_id IN (2004, 2005); +DELETE FROM invite_list WHERE round_id = 2005; +DELETE FROM round WHERE round_id IN (2001, 2002, 2003, 2004, 2005, 2006, 2007); +DELETE FROM team_coder_xref WHERE coder_id IN (132456); + +DELETE FROM contest WHERE contest_id IN (10000, 10001); diff --git a/test/sqls/marathonRegInfo/informixoltp__insert_test_data b/test/sqls/marathonRegInfo/informixoltp__insert_test_data new file mode 100755 index 000000000..0f8190117 --- /dev/null +++ b/test/sqls/marathonRegInfo/informixoltp__insert_test_data @@ -0,0 +1,61 @@ +INSERT INTO contest (contest_id, name, status, group_id) VALUES (10000, 'Marathon Match 90', 'A', -1); +INSERT INTO contest (contest_id, name, status, group_id) VALUES (10001, 'Marathon Match 91', 'A', -1); + +INSERT INTO round(round_id, contest_id, name, status, round_type_id, short_name, invitational, registration_limit) VALUES(2001, 10000, 'Round 2001', 'A', 13, 'Test Round 2001', 0, 10); +INSERT INTO round(round_id, contest_id, name, status, round_type_id, short_name, invitational, registration_limit) VALUES(2002, 1000, 'Test Round 2002', 'A', 13, 'Test Round 2002', 0, 10); +INSERT INTO round(round_id, contest_id, name, status, round_type_id, short_name, invitational, registration_limit) VALUES(2003, 1000, 'Test Round 2003', 'A', 13, 'Test Round 2003', 1, 10); +INSERT INTO round(round_id, contest_id, name, status, round_type_id, short_name, invitational, registration_limit) VALUES(2004, 1000, 'Test Round 2004', 'A', 13, 'Test Round 2004', 0, 10); +INSERT INTO round(round_id, contest_id, name, status, round_type_id, short_name, invitational, registration_limit) VALUES(2005, 1000, 'Test Round 2005', 'A', 13, 'Test Round 2005', 1, 10); +INSERT INTO round(round_id, contest_id, name, status, round_type_id, short_name, invitational, registration_limit) VALUES(2006, 10001, 'Test Round 2006', 'A', 13, 'Test Round 2006', 0, 10); +INSERT INTO round(round_id, contest_id, name, status, round_type_id, short_name, invitational, registration_limit) VALUES(2007, 10001, 'Test Round 2007', 'A', 13, 'Test Round 2007', 0, 10); + +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2001, 1, current - 50 units day, current + 30 units day, 'P'); +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2001, 2, current - 50 units day, current + 30 units day, 'P'); + +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2002, 1, current - 50 units day, current - 1 units day, 'P'); +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2002, 2, current - 50 units day, current - 1 units day, 'P'); + +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2003, 1, current - 50 units day, current + 30 units day, 'P'); +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2003, 2, current - 50 units day, current + 30 units day, 'P'); + +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2004, 1, current - 50 units day, current + 30 units day, 'P'); +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2004, 2, current - 50 units day, current + 60 units day, 'P'); + +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2005, 1, current - 50 units day, current + 30 units day, 'P'); +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2005, 2, current - 50 units day, current + 60 units day, 'P'); + +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2006, 1, current - 50 units day, current + 30 units day, 'P'); +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2006, 2, current - 50 units day, current + 30 units day, 'P'); + +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2007, 1, current - 50 units day, current + 30 units day, 'P'); +INSERT INTO round_segment(round_id, segment_id, start_time, end_time, status) VALUES(2007, 2, current - 50 units day, current + 30 units day, 'P'); + +INSERT INTO round_component(round_id, component_id, division_id) VALUES(2004, 2021, 1); +INSERT INTO round_component(round_id, component_id, division_id) VALUES(2005, 2021, 1); + +INSERT INTO invite_list(coder_id, round_id) VALUES(124772, 2005); + +INSERT INTO round_event(round_id, event_id, event_name, registration_url) VALUES(2001, 2001, 'Test Event 2001', 'https://foo.com'); +INSERT INTO round_event(round_id, event_id, event_name, registration_url) VALUES(2006, 2001, 'Test Event 2006', 'https://foo.com'); +INSERT INTO round_event(round_id, event_id, event_name, registration_url) VALUES(2007, 2001, 'Test Event 2007', 'https://foo.com'); + +INSERT INTO round_registration(round_id, coder_id, timestamp, eligible, team_id) +VALUES(2001, 124766, current, 1, null); +INSERT INTO team_coder_xref(team_id, coder_id, create_date) VALUES(36024, 132456, current); + +INSERT INTO round_terms(round_id) VALUES(2001); +INSERT INTO round_terms(round_id) VALUES(2006); + +INSERT INTO question(question_id, question_text, status_id, question_type_id, question_style_id) VALUES (1000, 'question 1000', 1, 2, 2); +INSERT INTO question(question_id, question_text, status_id, question_type_id, question_style_id) VALUES (1001, 'question 1001', 1, 3, 4); +INSERT INTO question(question_id, question_text, status_id, question_type_id, question_style_id) VALUES (1002, 'question 1002', 1, 2, 1); + +INSERT INTO round_question(round_id, question_id) VALUES (2001, 1000); +INSERT INTO round_question(round_id, question_id) VALUES (2001, 1001); +INSERT INTO round_question(round_id, question_id) VALUES (2001, 1002); + +INSERT INTO answer(answer_id, question_id, answer_text, sort_order, correct) VALUES (1001, 1000, 'answer text 2', 2, 0); +INSERT INTO answer(answer_id, question_id, answer_text, sort_order, correct) VALUES (1002, 1000, 'answer text 1', 1, 1); +INSERT INTO answer(answer_id, question_id, answer_text, sort_order, correct) VALUES (1003, 1000, 'answer text 3', 3, NULL); +INSERT INTO answer(answer_id, question_id, answer_text, sort_order, correct) VALUES (1004, 1002, 'answer text 4', NULL, 0); +INSERT INTO answer(answer_id, question_id, answer_text, sort_order, correct) VALUES (1005, 1002, 'answer text 5', 1, 1); diff --git a/test/test.marathonRegInfo.js b/test/test.marathonRegInfo.js new file mode 100755 index 000000000..1af1da449 --- /dev/null +++ b/test/test.marathonRegInfo.js @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2014 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSASSEMBLER + */ +'use strict'; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint node: true, stupid: true, unparam: true */ + +/** + * Module dependencies. + */ +var request = require('supertest'); +var assert = require('chai').assert; +var async = require('async'); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + '/sqls/marathonRegInfo/'; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; + +describe('Marathon Match Challenge Reg Info API', function () { + this.timeout(120000); // The api with testing remote db could be quit slow + + var heffan = testHelper.generateAuthHeader({ sub: 'ad|132456' }), + member1 = testHelper.generateAuthHeader({ sub: 'ad|132457' }), + member2 = testHelper.generateAuthHeader({ sub: 'ad|132458' }), + member3 = testHelper.generateAuthHeader({ sub: 'ad|124764' }), + member4 = testHelper.generateAuthHeader({ sub: 'ad|124772' }), + member5 = testHelper.generateAuthHeader({ sub: 'ad|124766' }), + forbiddenUser = testHelper.generateAuthHeader({ sub: 'ad|300001' }), + unActivatedUser = testHelper.generateAuthHeader({ sub: 'ad|300002' }), + notExistedUser = testHelper.generateAuthHeader({ sub: 'ad|1234567890' }), + iranUser = testHelper.generateAuthHeader({ sub: 'ad|300003' }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + async.waterfall([ + function (cb) { + testHelper.runSqlFile(SQL_DIR + 'informixoltp__clean', 'informixoltp', cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + 'common_oltp__clean', 'common_oltp', cb); + } + ], done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + clearDb, + function (cb) { + testHelper.runSqlFile(SQL_DIR + 'common_oltp__insert_test_data', 'common_oltp', cb); + }, + function (cb) { + testHelper.runSqlFile(SQL_DIR + 'informixoltp__insert_test_data', 'informixoltp', cb); + }, + function (cb) { + testHelper.updateTextColumn('update round_terms set terms_content = ? where round_id IN (2001, 2006)', 'informixoltp', [{type: 'text', value : 'Marathon Match terms content'}], cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create a http request. + * @param {String} roundId - the request roundId. + * @param {Object} authHeader - the auth header for request. + */ + function createRequest(roundId, authHeader) { + var req = request(API_ENDPOINT) + .get('/v2/data/marathon/challenges/' + roundId + '/regInfo') + .set('Accept', 'application/json'); + if (authHeader) { + req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/); + } + + /** + * Helper method for validating marathon match register information + * + * @param {String} roundId - the request roundId. + * @param {Object} authHeader - the auth header for request. + * @param {String} expectFile - the expect file path + * @param {Function} done - the callback function + */ + function validateResult(roundId, authHeader, expectFile, done) { + createRequest(roundId, authHeader).expect(200).end(function (err, res) { + if (err) { + done(err); + return; + } + var expected = require(expectFile); + delete res.body.serverInformation; + delete res.body.requesterInformation; + assert.deepEqual(res.body, expected, "Invalid response"); + done(); + }); + } + + /** + * Assert error request. + * + * @param {String} roundId - the request roundId. + * @param {Object} authHeader - the auth header for request. + * @param {Number} statusCode - the expected status code + * @param {String} errorDetail - the error detail. + * @param {Function} done the callback function + */ + function assertError(roundId, authHeader, statusCode, errorDetail, done) { + createRequest(roundId, authHeader).expect(statusCode).end(function (err, res) { + if (err) { + done(err); + return; + } + assert.equal(res.body.error.details, errorDetail, "Invalid error detail"); + done(); + }); + } + + /** + * Test when authorization is missing. + */ + it('should return authorized error. The authorization is missing.', function (done) { + assertError('2001', null, 401, 'Authorization information needed or incorrect.', done); + }); + + /** + * Test when roundId is not number. + */ + it('should return bad request. The roundId is not number.', function (done) { + assertError('abc', member1, 400, 'roundId should be number.', done); + }); + + /** + * Test when roundId is not integer. + */ + it('should return bad request. The roundId is not integer.', function (done) { + assertError('1.234', member1, 400, 'roundId should be Integer.', done); + }); + + /** + * Test when roundId is not positive. + */ + it('should return bad request. The roundId is not positive.', function (done) { + assertError('-1', member1, 400, 'roundId should be positive.', done); + }); + + /** + * Test when roundId is zero. + */ + it('should return bad request. The roundId is zero.', function (done) { + assertError('0', member1, 400, 'roundId should be positive.', done); + }); + + /** + * Test when roundId is too big. + */ + it('should return bad request. The roundId is too big.', function (done) { + assertError('2147483648', member1, 400, 'roundId should be less or equal to 2147483647.', done); + }); + + /** + * Test when user is forbidden to access register endpoint. + */ + it('should return bad request. The user don\'t have the access to register endpoint.', function (done) { + assertError('2001', forbiddenUser, 403, 'The user is forbidden to access this endpoint.', done); + }); + + /** + * Test when user is not activated. + */ + it('should return bad request. The user is not activated.', function (done) { + assertError('2001', unActivatedUser, 400, 'You are not eligible to participate in this competition.', done); + }); + + /** + * Test when user is not existed. + */ + it('should return bad request. The user is not existed.', function (done) { + assertError('2001', notExistedUser, 500, 'user not found with id=1234567890', done); + }); + + /** + * Test when round is not existed. + */ + it('should return bad request. The challenge round is not existed.', function (done) { + assertError('3001', member1, 400, 'Round doesn\'t exist 3001.', done); + }); + + /** + * Test when round has a event but the user didn't register it. + */ + it('should return bad request. The event not registered.', function (done) { + assertError('2001', member2, 400, 'In order to participate in this competition, you must register for ' + + 'Test Event 2001. Registration is available: here. ' + + 'Please register at the provided URL first and then repeat registration at Marathon Match Active Contests page.', done); + }); + + /** + * Test when registration of round event is not eligible. + */ + it('should return bad request. The registration of event is not eligible.', function (done) { + assertError('2001', member3, 400, 'You are not eligible to participate in this competition.', done); + }); + + /** + * Test when round registration is closed. + */ + it('should return bad request. The round registration is closed.', function (done) { + assertError('2002', member1, 400, 'Registration is not currently open.', done); + }); + + /** + * Test when user is in a invalid country. + */ + it('should return bad request. The user is in invalid country.', function (done) { + assertError('2001', iranUser, 400, 'You are not eligible to participate in this competition. Please contact support@topcoder.com if you have any questions.', done); + }); + + /** + * Test when round is required invitation and the user don't have one. + */ + it('should return bad request. The user is not invited to this challenge.', function (done) { + assertError('2003', member1, 400, 'Sorry, this round is by invitation only.', done); + }); + + /** + * Test when the round is parallel round. + */ + it('should return bad request. The round is parallel round.', function (done) { + assertError('2004', member4, 400, 'Sorry, you can not register for this round, you must compete in the version of this round that you were invited to.', done); + }); + + /** + * Test when the round term doesn't exist. + */ + it('should return not found. The round term doesn\'t exist.', function (done) { + assertError('2007', heffan, 404, 'Could not find specified round terms.', done); + }); + + /** + * Test when the round meet registration limit. + */ + it('should return bad request. The round has no empty positions for registrants.', function (done) { + async.waterfall([ + function (cb) { + testHelper.runSqlQuery('UPDATE round SET registration_limit = 0 WHERE round_id = 2001', 'informixoltp', cb); + }, + function (cb) { + assertError('2001', heffan, 400, 'There are no more spots available for the round.', cb); + }, + function (cb) { + testHelper.runSqlQuery('UPDATE round SET registration_limit = 10 WHERE round_id = 2001', 'informixoltp', cb); + } + ], done); + }); + + /** + * Get marathon match register information for round 2001. + * Expect success results. + */ + it('should return success results for round 2001', function (done) { + validateResult('2001', heffan, './test_files/expected_marathon_reg_info.json', done); + }); + + /** + * Get marathon match register information for round 2001. + * user twight already register round 2001, expect success results. + */ + it('should return success results for round 2001 when user already register', function (done) { + validateResult('2001', member5, './test_files/expected_marathon_reg_info.json', done); + }); + + /** + * Get marathon match register information for round 2006. + * Expect success results with empty questions. + */ + it('should return success results with empty questions for round 2006', function (done) { + validateResult('2006', heffan, './test_files/expected_marathon_reg_info_empty_questions.json', done); + }); +}); diff --git a/test/test_files/expected_marathon_reg_info.json b/test/test_files/expected_marathon_reg_info.json new file mode 100755 index 000000000..1a1961473 --- /dev/null +++ b/test/test_files/expected_marathon_reg_info.json @@ -0,0 +1,62 @@ +{ + "term": { + "contestName": "Marathon Match 90", + "roundName": "Round 2001", + "termsContent": "Marathon Match terms content" + }, + "questions": [ + { + "id": 1000, + "style": "Multiple Choice", + "type": "Elligble", + "text": "question 1000", + "answers": [ + { + "id": 1002, + "text": "answer text 1", + "sortOrder": 1, + "correct": true + }, + { + "id": 1001, + "text": "answer text 2", + "sortOrder": 2, + "correct": false + }, + { + "id": 1003, + "text": "answer text 3", + "sortOrder": 3, + "correct": false + } + ] + }, + { + "id": 1001, + "style": "Short Answer", + "type": "Personal", + "text": "question 1001", + "answers": [] + }, + { + "id": 1002, + "style": "Single Choice", + "type": "Elligble", + "text": "question 1002", + "answers": [ + { + "id": 1004, + "text": "answer text 4", + "sortOrder": -1, + "correct": false + }, + { + "id": 1005, + "text": "answer text 5", + "sortOrder": 1, + "correct": true + } + ] + } + ] +} \ No newline at end of file diff --git a/test/test_files/expected_marathon_reg_info_empty_questions.json b/test/test_files/expected_marathon_reg_info_empty_questions.json new file mode 100755 index 000000000..2191f8339 --- /dev/null +++ b/test/test_files/expected_marathon_reg_info_empty_questions.json @@ -0,0 +1,8 @@ +{ + "term": { + "contestName": "Marathon Match 91", + "roundName": "Test Round 2006", + "termsContent": "Marathon Match terms content" + }, + "questions": [] +} \ No newline at end of file