diff --git a/actions/srmRoundQuestions.js b/actions/srmRoundQuestions.js index 10840e290..f922c4673 100644 --- a/actions/srmRoundQuestions.js +++ b/actions/srmRoundQuestions.js @@ -1,13 +1,14 @@ /* * Copyright (C) 2014 TopCoder Inc., All Rights Reserved. - */ - /** - * - Implement the srm round questions / answers / survey api. + * + * @version 1.2 + * @author isv + * + * - Implement the srm round questions / answers / survey api. * Changes in version 1.1 (Module Assembly - Web Arena - Match Configurations): * - Updated getRoundQuestions to send roundId with response - * - * @version 1.1 - * @author TCSASSEMBLER + * Changes in 1.2: + * - Added actions for modifying/deleting round question answers. */ /*jslint node: true, nomen: true, plusplus: true, stupid: true, unparam: true */ @@ -16,6 +17,7 @@ var async = require('async'); var _ = require('underscore'); var moment = require('moment'); var IllegalArgumentError = require('../errors/IllegalArgumentError'); +var NotFoundError = require('../errors/NotFoundError'); var DATE_FORMAT = "YYYY-MM-DD HH:mm"; @@ -327,7 +329,8 @@ function checkAnswerValues(api, text, sortOrder, correct, callback) { error = helper.checkStringParameter(text, "text", 250); if (!error && _.isDefined(sortOrder)) { - error = helper.checkPositiveInteger(sortOrder, "sortOrder"); + error = helper.checkPositiveInteger(sortOrder, "sortOrder") + || helper.checkMaxInt(sortOrder, "sortOrder"); } if (!error && _.isDefined(correct)) { @@ -609,8 +612,8 @@ var modifyRoundQuestion = function (api, connection, dbConnectionMap, next) { */ var deleteRoundQuestion = function (api, connection, dbConnectionMap, next) { var helper = api.helper, - sqlParams = {}, - questionId = Number(connection.params.questionId); + sqlParams = {}, + questionId = Number(connection.params.questionId); async.waterfall([ function (cb) { @@ -631,6 +634,115 @@ var deleteRoundQuestion = function (api, connection, dbConnectionMap, next) { }); }; +/** + * Checks if answer with specified ID exists in database. + * + * @param api the api instance. + * @param dbConnectionMap the database connection map. + * @param answerId - the answerId parameter. + * @param callback the callback method. + */ +function checkAnswerId(api, dbConnectionMap, answerId, callback) { + var helper = api.helper, + error = helper.checkIdParameter(answerId, "answerId"); + + async.waterfall([ + function (cb) { + if (!error) { + api.dataAccess.executeQuery("get_answer_id", {answerId: answerId}, dbConnectionMap, cb); + } else { + cb(null, null); + } + }, function (results, cb) { + if (!error) { + if (results.length === 0) { + error = new NotFoundError("The answerId does not exist in database."); + } + } + cb(error); + } + ], function (err) { + if (err) { + callback(err); + return; + } + + callback(null, error); + }); +} + +/** + * Modify Round Question Answer. + * + * @param api the api instance. + * @param connection the connection instance. + * @param dbConnectionMap the database connection map. + * @param next the callback method. + */ +var modifyRoundQuestionAnswer = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, + sqlParams = {}, + answerId = Number(connection.params.answerId), + text = connection.params.text, + sortOrder = connection.params.sortOrder, + correct = connection.params.correct; + + async.waterfall([ + function (cb) { + cb(helper.checkAdmin(connection, 'Authorized information needed.', 'Admin access only.')); + }, function (cb) { + checkAnswerValues(api, text, sortOrder, correct, cb); + }, function (error, cb) { + checkAnswerId(api, dbConnectionMap, answerId, cb); + }, function (error, cb) { + sqlParams.answerId = answerId; + sqlParams.answerText = text; + sqlParams.sortOrder = sortOrder; + sqlParams.correct = (correct === true || correct.toLowerCase() === "true") ? 1 : 0; + api.dataAccess.executeQuery("update_answer", sqlParams, dbConnectionMap, cb); + } + ], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = {"success": true}; + } + next(connection, true); + }); +}; + +/** + * Delete Round Question Answer. + * + * @param api the api instance. + * @param connection the connection instance. + * @param dbConnectionMap the database connection map. + * @param next the callback method. + */ +var deleteRoundQuestionAnswer = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, + sqlParams = {}, + answerId = Number(connection.params.answerId); + + async.waterfall([ + function (cb) { + cb(helper.checkAdmin(connection, 'Authorized information needed.', 'Admin access only.')); + }, function (cb) { + cb(helper.checkIdParameter(answerId, 'answerId')); + }, function (cb) { + sqlParams.answerId = answerId; + api.dataAccess.executeQuery("delete_answer", sqlParams, dbConnectionMap, cb); + } + ], function (err, result) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = {"success": result > 0}; + } + next(connection, true); + }); +}; + /** * The API for get Round Questions API. */ @@ -802,3 +914,53 @@ exports.deleteRoundQuestion = { } } }; + +/** + * The API for Modify Round Question Answer API. + */ +exports.modifyRoundQuestionAnswer = { + name: "modifyRoundQuestionAnswer", + description: "Modify Round Question Answer", + inputs: { + required: ['answerId', 'text', 'sortOrder', 'correct'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'write', + databases: ["informixoltp"], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute modifyRoundQuestionAnswer#run", 'debug'); + modifyRoundQuestionAnswer(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * The API for Delete Round Question Answer API. + */ +exports.deleteRoundQuestionAnswer = { + name: "deleteRoundQuestionAnswer", + description: "Delete Round Question Answer", + inputs: { + required: ['answerId'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'write', + databases: ["informixoltp"], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute deleteRoundQuestionAnswer#run", 'debug'); + deleteRoundQuestionAnswer(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; diff --git a/apiary.apib b/apiary.apib index a7477f3e3..deab967a9 100644 --- a/apiary.apib +++ b/apiary.apib @@ -14431,3 +14431,207 @@ Managing SRM Round Configuration APIs "description":"Servers are up but overloaded. Try again later." } } + +# Group Round Question Answers APIs +Managing Round Question Answers APIs + +## Manage Round Question Answer API [/data/srm/answer/:answerId] + +### Modify Round Question Answer API [PUT] + ++ Parameters + + answerId (required, integer) ... answer ID + + text (required, integer) ... answer text + + sortOrder (required, integer) ... the sorting order + + correct (required, boolean) ... flag indicating whether answer is correct or not. + ++ Response 200 (application/json) + + { + "success": true + } + ++ Response 200 (application/json) + + { + "error": "Error: text is a required parameter for this action" + } + ++ Response 200 (application/json) + + { + "error": "Error: sortOrder is a required parameter for this action" + } + ++ Response 200 (application/json) + + { + "error": "Error: correct is a required parameter for this action" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"answerId should be positive." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"answerId should be number." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"answerId should be less or equal to 2147483647." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"text exceeds 250 characters." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"sortOrder should be positive." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"sortOrder should be number." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"sortOrder should be less or equal to 2147483647." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The correct should be boolean type." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authorized information needed." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"Admin access only." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The answerId does not exist in database." + } + + ++ 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." + } + +## Delete Round Question Answer API [/data/srm/answer/:answerId] + +### Delete Round Question Answer API [DELETE] + ++ Parameters + + answerId (required, integer) ... deleting answer's id + ++ Response 200 (application/json) + + { + "success": true + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"answerId should be positive." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"answerId should be number." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authorized information needed." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"Admin access only." + } + ++ 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/Module Assembly - Modify Answer API And Delete Answer API Deployment Guide.doc b/docs/Module Assembly - Modify Answer API And Delete Answer API Deployment Guide.doc new file mode 100644 index 000000000..80b4613a4 Binary files /dev/null and b/docs/Module Assembly - Modify Answer API And Delete Answer API Deployment Guide.doc differ diff --git a/queries/delete_answer b/queries/delete_answer new file mode 100644 index 000000000..82ca7c4d8 --- /dev/null +++ b/queries/delete_answer @@ -0,0 +1 @@ +DELETE FROM answer WHERE answer_id = @answerId@ diff --git a/queries/delete_answer.json b/queries/delete_answer.json new file mode 100644 index 000000000..e3d65eb2a --- /dev/null +++ b/queries/delete_answer.json @@ -0,0 +1,5 @@ +{ + "name": "delete_answer", + "db": "informixoltp", + "sqlfile": "delete_answer" +} diff --git a/queries/get_answer_id b/queries/get_answer_id new file mode 100644 index 000000000..c9d1fe5c0 --- /dev/null +++ b/queries/get_answer_id @@ -0,0 +1 @@ +SELECT answer_id FROM answer WHERE answer_id = @answerId@ diff --git a/queries/get_answer_id.json b/queries/get_answer_id.json new file mode 100644 index 000000000..1f5bed4a3 --- /dev/null +++ b/queries/get_answer_id.json @@ -0,0 +1,5 @@ +{ + "name": "get_answer_id", + "db": "informixoltp", + "sqlfile": "get_answer_id" +} diff --git a/queries/update_answer b/queries/update_answer new file mode 100644 index 000000000..f7b34acf0 --- /dev/null +++ b/queries/update_answer @@ -0,0 +1,5 @@ +UPDATE answer +SET answer_text = '@answerText@', + sort_order = @sortOrder@, + correct = @correct@ +WHERE answer_id = @answerId@ diff --git a/queries/update_answer.json b/queries/update_answer.json new file mode 100644 index 000000000..f6e4b373c --- /dev/null +++ b/queries/update_answer.json @@ -0,0 +1,5 @@ +{ + "name": "update_answer", + "db": "informixoltp", + "sqlfile": "update_answer" +} diff --git a/routes.js b/routes.js index 73ea6d25c..f2f94f0a4 100755 --- a/routes.js +++ b/routes.js @@ -1,7 +1,7 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.60 + * @version 1.61 * @author vangavroche, Sky_, muzehyun, kurtrips, Ghost_141, ecnu_haozi, hesibo, LazyChild, isv, flytoj2ee, * @author panoptimum, bugbuka, Easyhard * @@ -140,6 +140,8 @@ * - Add route for user activation email api. * Changes in 1.60: * - Add route for get user identity api. + * Changes in 1.61: + * - Added routes for modifying/deleting round question answers. */ /*jslint node:true, nomen: true */ "use strict"; @@ -330,9 +332,9 @@ exports.routes = { { path: "/:apiVersion/data/srm/problems", action: "listSRMProblems" }, { path: "/:apiVersion/data/srm/rounds/:roundId/problems", action: "listRoundProblems" }, { path: "/:apiVersion/data/srm/rounds/:roundId/:problemId/:divisionId/components", action: "listRoundProblemComponents" }, - { path: "/:apiVersion/data/srm/rounds/:roundId/components", action: "listRoundProblemComponents" }, + { path: "/:apiVersion/data/srm/rounds/:roundId/components", action: "listRoundProblemComponents" }, { path: "/:apiVersion/data/srm/rounds/:roundId/terms", action: "getRoundTerms" }, - { path: "/:apiVersion/data/srm/rounds/:contestId", action: "listSRMContestRounds" }, + { path: "/:apiVersion/data/srm/rounds/:contestId", action: "listSRMContestRounds" }, { path: "/:apiVersion/auth0/callback", action: "auth0Callback" }, //Stubs APIs @@ -383,10 +385,12 @@ exports.routes = { put: [ { path: "/:apiVersion/data/srm/contests/:id", action: "updateSRMContest"}, - { path: "/:apiVersion/data/srm/rounds/:oldRoundId", action: "modifySRMContestRound" } + { path: "/:apiVersion/data/srm/rounds/:oldRoundId", action: "modifySRMContestRound" }, + { path: "/:apiVersion/data/srm/answer/:answerId", action: "modifyRoundQuestionAnswer"} ], delete: [ { path: "/:apiVersion/data/srm/rounds/:questionId/question", action: "deleteRoundQuestion" }, - { path: "/:apiVersion/data/srm/rounds/:roundId", action: "deleteSRMContestRound" } + { path: "/:apiVersion/data/srm/rounds/:roundId", action: "deleteSRMContestRound" }, + { path: "/:apiVersion/data/srm/answer/:answerId", action: "deleteRoundQuestionAnswer" } ] }; diff --git a/test/sqls/modifyDeleteAnswer/informixoltp__clean b/test/sqls/modifyDeleteAnswer/informixoltp__clean new file mode 100644 index 000000000..c1d140743 --- /dev/null +++ b/test/sqls/modifyDeleteAnswer/informixoltp__clean @@ -0,0 +1,2 @@ +DELETE FROM answer WHERE answer_id BETWEEN 1001 AND 1005; +DELETE FROM question WHERE question_id BETWEEN 1000 AND 1002; diff --git a/test/sqls/modifyDeleteAnswer/informixoltp__insert_test_data b/test/sqls/modifyDeleteAnswer/informixoltp__insert_test_data new file mode 100644 index 000000000..4f80a73dd --- /dev/null +++ b/test/sqls/modifyDeleteAnswer/informixoltp__insert_test_data @@ -0,0 +1,9 @@ +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 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.modifyDeleteAnswers.js b/test/test.modifyDeleteAnswers.js new file mode 100644 index 000000000..2cf8900f8 --- /dev/null +++ b/test/test.modifyDeleteAnswers.js @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2014 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author isv + */ +"use strict"; + +/*global describe, it, before, beforeEach, after, afterEach, __dirname */ +/*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 API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var SQL_DIR = __dirname + "/sqls/modifyDeleteAnswer/"; + +describe('Test Modify/Delete Round Question Answer API', function () { + + var heffan = testHelper.generateAuthHeader({ sub: 'ad|132456' }), + wyzmo = testHelper.generateAuthHeader({ sub: 'ad|124856' }); + + this.timeout(120000); + + /** + * Clears the database. + * + * @param {Function} done the callback. + */ + function clearDb(done) { + async.waterfall([ + function (cb) { + testHelper.runSqlFile(SQL_DIR + "informixoltp__clean", 'informixoltp', 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 + "informixoltp__insert_test_data", "informixoltp", cb); + } + ], done); + }); + + /** + * This function is run after all tests. Clean up all data. + * + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Tests the Modify Round Question Answer action against failure test case. Posts a request for modifying the answer + * with specified data and expects the server to respond with HTTP response of specified status providing the + * specified expected error details. + * + * @param {Object} user - user account to cal the action. + * @param {String} answerId - answer ID parameter. + * @param {String} text - text parameter. + * @param {String} sortOrder - sort order parameter. + * @param {String} correct - correct parameter. + * @param {Number} expectedStatusCode - status code for HTTP response expected to be returned from server. + * @param {String} expectedErrorMessage - error message expected to be returned from server. + * @param {Function} callback - a callback to be called when test finishes. + */ + function testModifyFailureScenario(user, answerId, text, sortOrder, correct, expectedStatusCode, + expectedErrorMessage, callback) { + var queryParams = '?'; + if (text !== null) { + queryParams += 'text=' + text; + } + if (sortOrder !== null) { + queryParams += '&sortOrder=' + sortOrder; + } + if (correct !== null) { + queryParams += '&correct=' + correct; + } + + if (user !== null) { + request(API_ENDPOINT) + .put('/v2/data/srm/answer/' + answerId + queryParams) + .set('Accept', 'application/json') + .set('Authorization', user) + .expect('Content-Type', /json/) + .expect(expectedStatusCode) + .end(function (err, res) { + if (err) { + callback(err); + return; + } + var body = res.body; + if (expectedStatusCode === 200) { + assert.equal(body.error, expectedErrorMessage); + } else { + assert.equal(body.error.details, expectedErrorMessage); + } + callback(); + }); + } else { + request(API_ENDPOINT) + .put('/v2/data/srm/answer/' + answerId + queryParams) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(expectedStatusCode) + .end(function (err, res) { + if (err) { + callback(err); + return; + } + var body = res.body; + if (expectedStatusCode === 200) { + assert.equal(body.error, expectedErrorMessage); + } else { + assert.equal(body.error.details, expectedErrorMessage); + } + callback(); + }); + } + } + + /** + * Tests the Delete Round Question Answer action against failure test case. Posts a request for deleting the answer + * with specified data and expects the server to respond with HTTP response of specified status providing the + * specified expected error details. + * + * @param {Object} user - user account to cal the action. + * @param {String} answerId - answer ID parameter. + * @param {Number} expectedStatusCode - status code for HTTP response expected to be returned from server. + * @param {String} expectedErrorMessage - error message expected to be returned from server. + * @param {Function} callback - a callback to be called when test finishes. + */ + function testDeleteFailureScenario(user, answerId, expectedStatusCode, expectedErrorMessage, callback) { + if (user !== null) { + request(API_ENDPOINT) + .del('/v2/data/srm/answer/' + answerId) + .set('Accept', 'application/json') + .set('Authorization', user) + .expect('Content-Type', /json/) + .expect(expectedStatusCode) + .end(function (err, res) { + if (err) { + callback(err); + return; + } + var body = res.body; + if (expectedStatusCode === 200) { + assert.equal(body.error, expectedErrorMessage); + } else { + assert.equal(body.error.details, expectedErrorMessage); + } + callback(); + }); + } else { + request(API_ENDPOINT) + .del('/v2/data/srm/answer/' + answerId) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(expectedStatusCode) + .end(function (err, res) { + if (err) { + callback(err); + return; + } + var body = res.body; + if (expectedStatusCode === 200) { + assert.equal(body.error, expectedErrorMessage); + } else { + assert.equal(body.error.details, expectedErrorMessage); + } + callback(); + }); + } + } + + /** + * Tests the Modify Round Question Answer action against successful test case. Posts a request for modifying the + * answer based on specified parameters and expects the server to respond with HTTP response of 200 status and + * successful result. + * + * @param {Object} user - user account to cal the action. + * @param {String} answerId - answer ID parameter. + * @param {String} text - text parameter. + * @param {String} sortOrder - sort order parameter. + * @param {String} correct - correct parameter. + * @param {Function} callback - a callback to be called when test finishes. + */ + function testModifySuccessScenario(user, answerId, text, sortOrder, correct, callback) { + var queryParams = '?'; + if (text !== null) { + queryParams += 'text=' + text; + } + if (sortOrder !== null) { + queryParams += '&sortOrder=' + sortOrder; + } + if (correct !== null) { + queryParams += '&correct=' + correct; + } + + request(API_ENDPOINT) + .put('/v2/data/srm/answer/' + answerId + queryParams) + .set('Accept', 'application/json') + .set('Authorization', user) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err, res) { + if (err) { + callback(err); + return; + } + + var body = res.body; + + assert.isTrue(body.success, "Invalid response on successful answer modification"); + + async.waterfall([ + function (cb) { + testHelper.runSqlSelectQuery('* FROM answer WHERE answer_id = ' + answerId, 'informixoltp', cb); + }, function (result, cb) { + assert.equal(result.length, 1, 'Answer is not found in database'); + assert.equal(result[0].answer_text, text, 'Answer text is not saved correctly'); + assert.equal(result[0].sort_order, sortOrder, 'Answer sort order is not saved correctly'); + assert.equal(result[0].correct, correct ? 1 : 0, 'Answer correct is not saved correctly'); + cb(); + } + ], callback); + }); + } + + /** + * Tests the Delete Round Question Answer action against successful test case. Posts a request for deleting the + * answer based on specified parameters and expects the server to respond with HTTP response of 200 status and + * successful result. + * + * @param {Object} user - user account to cal the action. + * @param {String} answerId - answer ID parameter. + * @param {Boolean} success - expected result. + * @param {Function} callback - a callback to be called when test finishes. + */ + function testDeleteSuccessScenario(user, answerId, success, callback) { + request(API_ENDPOINT) + .del('/v2/data/srm/answer/' + answerId) + .set('Accept', 'application/json') + .set('Authorization', user) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err, res) { + if (err) { + callback(err); + return; + } + var body = res.body; + + assert.equal(body.success, success, "Invalid response on successful answer deletion"); + + async.waterfall([ + function (cb) { + testHelper.runSqlSelectQuery('1 FROM answer WHERE answer_id = ' + answerId, 'informixoltp', cb); + }, function (result, cb) { + assert.equal(result.length, 0, 'Answer is not deleted from database'); + cb(); + } + ], callback); + }); + } + + // Failure test cases + it('MODIFY: Anonymous access', function (done) { + testModifyFailureScenario(null, 1, 'Text', 1, 'true', 401, 'Authorized information needed.', done); + }); + + it('MODIFY: Non-admin access', function (done) { + testModifyFailureScenario(wyzmo, 1, 'Text', 1, 'true', 403, 'Admin access only.', done); + }); + + it('MODIFY: Negative answerId parameter', function (done) { + testModifyFailureScenario(heffan, -1, 'Text', 1, 'true', 400, 'answerId should be positive.', done); + }); + + it('MODIFY: Zero answerId parameter', function (done) { + testModifyFailureScenario(heffan, 0, 'Text', 1, 'true', 400, 'answerId should be positive.', done); + }); + + it('MODIFY: Too large answerId parameter', function (done) { + testModifyFailureScenario(heffan, 2147483648, 'Text', 1, 'true', 400, + 'answerId should be less or equal to 2147483647.', done); + }); + + it('MODIFY: Non-numeric answerId parameter', function (done) { + testModifyFailureScenario(heffan, 'a', 'Text', 1, 'true', 400, 'answerId should be number.', done); + }); + + it('MODIFY: No text parameter', function (done) { + testModifyFailureScenario(heffan, 1001, null, 1, 'true', 200, + 'Error: text is a required parameter for this action', done); + }); + + it('MODIFY: Empty text parameter', function (done) { + testModifyFailureScenario(heffan, 1001, '', 1, 'true', 200, + 'Error: text is a required parameter for this action', done); + }); + + it('MODIFY: Too long text parameter', function (done) { + var i, + longText = ''; + + for (i = 0; i < 300; i = i + 1) { + longText += 'x'; + } + + testModifyFailureScenario(heffan, 1001, longText, 1, 'true', 400, + 'text exceeds 250 characters.', done); + }); + + it('MODIFY: No sortOrder parameter', function (done) { + testModifyFailureScenario(heffan, 1001, 'Text', null, 'true', 200, + 'Error: sortOrder is a required parameter for this action', done); + }); + + it('MODIFY: Empty sortOrder parameter', function (done) { + testModifyFailureScenario(heffan, 1001, 'Text', '', 'true', 200, + 'Error: sortOrder is a required parameter for this action', done); + }); + + it('MODIFY: Negative sortOrder parameter', function (done) { + testModifyFailureScenario(heffan, 1001, 'Text', -1, 'true', 400, 'sortOrder should be positive.', done); + }); + + it('MODIFY: Non-numeric sortOrder parameter', function (done) { + testModifyFailureScenario(heffan, 1001, 'Text', 'a', 'true', 400, 'sortOrder should be number.', done); + }); + + it('MODIFY: Too large sortOrder parameter', function (done) { + testModifyFailureScenario(heffan, 1001, 'Text', 2147483648, 'true', 400, + 'sortOrder should be less or equal to 2147483647.', done); + }); + + it('MODIFY: No correct parameter', function (done) { + testModifyFailureScenario(heffan, 1001, 'Text', 1, null, 200, + 'Error: correct is a required parameter for this action', done); + }); + + it('MODIFY: Empty correct parameter', function (done) { + testModifyFailureScenario(heffan, 1001, 'Text', 1, '', 200, + 'Error: correct is a required parameter for this action', done); + }); + + it('MODIFY: Invalid correct parameter', function (done) { + testModifyFailureScenario(heffan, 1001, 'Text', 1, 'neither', 400, + 'The correct should be boolean type.', done); + }); + + it('MODIFY: Unknown answerId parameter', function (done) { + testModifyFailureScenario(heffan, 200100, 'Text', 1, 'true', 404, + 'The answerId does not exist in database.', done); + }); + + it('DELETE: Anonymous access', function (done) { + testDeleteFailureScenario(null, 1, 401, 'Authorized information needed.', done); + }); + + it('DELETE: Non-admin access', function (done) { + testDeleteFailureScenario(wyzmo, 1, 403, 'Admin access only.', done); + }); + + it('DELETE: Negative answerId parameter', function (done) { + testDeleteFailureScenario(heffan, -1, 400, 'answerId should be positive.', done); + }); + + it('DELETE: Zero answerId parameter', function (done) { + testDeleteFailureScenario(heffan, 0, 400, 'answerId should be positive.', done); + }); + + it('DELETE: Too large answerId parameter', function (done) { + testDeleteFailureScenario(heffan, 2147483648, 400, 'answerId should be less or equal to 2147483647.', done); + }); + + it('DELETE: Non-numeric answerId parameter', function (done) { + testDeleteFailureScenario(heffan, 'a', 400, 'answerId should be number.', done); + }); + + // Accuracy test cases + it('DELETE: existing answer', function (done) { + testDeleteSuccessScenario(heffan, 1001, true, done); + }); + + it('DELETE: non-existing answer', function (done) { + testDeleteSuccessScenario(heffan, 2001, false, done); + }); + + it('MODIFY: existing answer', function (done) { + testModifySuccessScenario(heffan, 1002, 'NEW TEXT', 5, false, done); + }); +});