Commit c7ee583c authored by Matheus Horstmann's avatar Matheus Horstmann 🐴 Committed by Lucas Fernandes de Oliveira

Issue #21: Post Form Answer and Validate it

Signed-off-by: Matheus Horstmann's avatarMatheus Horstmann <mch15@inf.ufpr.br>
parent 9eb99caa
......@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.0.19 - 12-06-2019
### Added
- A route to POST a form Answer #21 (Horstmann)
- Create ValidationError Class that extends error class, with the objective to return a dictionary of invalid answers (Horstmann)
### Changed
- DbHandler's tests to suite with new forms answers added (Horstmann)
- ValidationHandler to validate a Forms Answer instead of inputsAnswer
- ValidationHandler's tests to suite new method to validate a Forms Answer
## 0.0.18 - 25-05-2019
### Added
- Create readFormAnswer method to read formAnswer from database #24 (Horstmann)
......@@ -12,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Fix OptHandler to return id in inputAnswer
## 0.0.17 - 25-05-2019
### Added
- inputAnswer method in OptHandler #23 (Horstmann)
......
{
"name": "form-creator-api",
"version": "0.0.18",
"version": "0.0.19",
"description": "RESTful API used to manage and answer forms.",
"main": "index.js",
"scripts": {
......
/*
* form-creator-api. RESTful API to manage and answer forms.
* Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
* Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR
*
* This file is part of form-creator-api.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as request from "supertest";
import { expect } from "chai";
import * as server from "../../main";
import { EnumHandler,InputType, ValidationType } from "../../utils/enumHandler";
import { TestHandler } from "../../utils/testHandler";
import { OptHandler } from "../../utils/optHandler";
import { Form, FormOptions } from "../../core/form";
import { Input, InputOptions, Validation } from "../../core/input";
describe("API data controller", () => {
it("should respond 200 when posting valid form Answer", (done) => {
request(server)
.post("/answer/1")
.send({
1:["Answer to Question 1 Form 1"]
, 2:["12345-000"]
, 3:["MAXCHAR 10"]
})
.expect(200)
.expect((res: any) => {
expect(res.body.id).to.be.equal(7);
expect(res.body.message).to.be.equal("Answered");
})
.end(done);
});
it("should respond 500 when posting invalid form Answer", (done) => {
request(server)
.post("/answer/1")
.send({
1:["Answer to Question 1 Form 1"]
, 2:["12a345-000"]
, 3:["MAXCHAR 10 AND MORE"]
})
.expect(500)
.expect((res: any) => {
const message = "Could not Create form Answer. Some error has occurred. Check error property for details.";
expect(res.body).to.be.an("object");
expect(res.body).to.have.property("error");
expect(res.body.message).to.be.equal(message);
expect(res.body.error["2"]).to.be.equal("RegEx do not match");
expect(res.body.error["3"]).to.be.equal("Input answer must be lower than 10");
})
.end(done);
});
});
/*
* form-creator-api. RESTful API to manage and answer forms.
* Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
* Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR
*
* This file is part of form-creator-api.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { OptHandler } from "../../utils/optHandler";
import { InputAnswerOptions, InputAnswerOptionsDict } from "../../core/inputAnswer";
import { Form, FormOptions } from "../../core/form";
import { FormAnswer, FormAnswerOptions } from "../../core/formAnswer";
import { ValidationHandler } from "../../utils/validationHandler";
import { Response, NextFunction } from "express";
import { Request } from "../apiTypes";
export class AnswerCtrl {
public static write(req: Request, res: Response, next: NextFunction) {
req.db.readForm(req.params.id, (err: Error, form?: Form) => {
if (err) {
res.status(500).json({
message: "Form with id: '" + req.params.id + "' not found. Some error has occurred. Check error property for details.",
error: err
});
return;
}
else {
let inputAnswerOptionsDict: InputAnswerOptionsDict = {}
for (const key of Object.keys(req.body)) {
inputAnswerOptionsDict[parseInt(key, 10)] = [];
for (const i in req.body[key]) {
const tmpInputAnswerOption: InputAnswerOptions = {
idInput: parseInt(key, 10)
, placement: parseInt(i, 10)
, value: req.body[key][i]
}
inputAnswerOptionsDict[parseInt(key, 10)].push(tmpInputAnswerOption);
}
}
let formAnswerOpt: FormAnswerOptions = {
form: form
, timestamp: new Date(Date.now())
, inputsAnswerOptions: inputAnswerOptionsDict
}
try{
const formAnswer: FormAnswer = new FormAnswer(OptHandler.formAnswer(formAnswerOpt));
ValidationHandler.validateFormAnswer(formAnswer);
req.db.writeFormAnswer(formAnswer, (err: Error, formAnswerResult: FormAnswer) => {
if (err){
throw err;
}
else{
res.json({
id: formAnswerResult.id
, message: "Answered"
});
return;
}
});
}
catch (e) {
if( e.validationDict !== undefined){
res.status(500).json({
message: "Could not Create form Answer. Some error has occurred. Check error property for details."
, error: e.validationDict
});
}else{
res.status(500).json({
message: "Could not Create form Answer. Some error has occurred. Check error property for details."
, error: e.message
});
}
return
}
}
});
}
}
......@@ -40,6 +40,7 @@ import { DbHandlerMw } from "./api/middlewares/dbHandler";
// Include controllers
import { FormCtrl } from "./api/controllers/form";
import { AnswerCtrl } from "./api/controllers/formAnswer";
// Setup middlewares
app.use("/", bodyParser.json());
......@@ -50,6 +51,7 @@ app.use("/", DbHandlerMw());
app.get("/forms/", FormCtrl.list);
app.get("/forms/:id", FormCtrl.read);
app.post("/forms", FormCtrl.write);
app.post("/answer/:id", AnswerCtrl.write);
// Listening
......
......@@ -267,10 +267,10 @@ describe("Database Handler", () => {
});
it("should insert a form answers", (done) => {
const queryString: string = "INSERT INTO form_answer(id, id_form, answered_at)\
const queryString: string = "INSERT INTO form_answer(id ,id_form, answered_at)\
VALUES\
(7, 2, '2018-07-02 10:10:25-03'),\
(8, 3, '2018-06-03 10:11:25-03');";
(8, 2, '2018-07-02 10:10:25-03'),\
(9, 3, '2018-06-03 10:11:25-03');";
const query: QueryOptions = {query: queryString, parameters: []};
dbhandler.executeQuery(query, (err: Error, result?: QueryResult) => {
......@@ -288,7 +288,7 @@ describe("Database Handler", () => {
dbhandler.executeQuery(query, (err: Error, result?: QueryResult) => {
expect(err).to.be.a("null");
expect(result.command).to.be.equal("SELECT");
expect(result.rowCount).to.be.equal(8);
expect(result.rowCount).to.be.equal(9);
done();
});
});
......@@ -306,7 +306,7 @@ describe("Database Handler", () => {
});
it("should remove existent form answers", (done) => {
const queryString: string = "DELETE FROM form_answer WHERE id=7 OR id=8;";
const queryString: string = "DELETE FROM form_answer WHERE id=8 OR id=9;";
const query: QueryOptions = {query: queryString, parameters: []};
dbhandler.executeQuery(query, (err: Error, result?: QueryResult) => {
......@@ -321,8 +321,8 @@ describe("Database Handler", () => {
it("should insert a input answers", (done) => {
const queryString: string = "INSERT INTO input_answer(id, id_form_answer, id_input, value, placement)\
VALUES\
(15,1, 6,'Answer to Question 1 Form 3',1),\
(16,1, 7,'Answer to Question 2 Form 3',2);";
(18,1, 6,'Answer to Question 1 Form 3',1),\
(19,1, 7,'Answer to Question 2 Form 3',2);";
const query: QueryOptions = {query: queryString, parameters: []};
dbhandler.executeQuery(query, (err: Error, result?: QueryResult) => {
......@@ -340,13 +340,13 @@ describe("Database Handler", () => {
dbhandler.executeQuery(query, (err: Error, result?: QueryResult) => {
expect(err).to.be.a("null");
expect(result.command).to.be.equal("SELECT");
expect(result.rowCount).to.be.equal(16);
expect(result.rowCount).to.be.equal(19);
done();
});
});
it("should remove non existent input answer", (done) => {
const queryString: string = "DELETE FROM input_answer WHERE id=17;";
const queryString: string = "DELETE FROM input_answer WHERE id=25;";
const query: QueryOptions = {query: queryString, parameters: []};
dbhandler.executeQuery(query, (err: Error, result?: QueryResult) => {
......@@ -358,7 +358,7 @@ describe("Database Handler", () => {
});
it("should remove existent input answers", (done) => {
const queryString: string = "DELETE FROM input_answer WHERE id=15 OR id=16;";
const queryString: string = "DELETE FROM input_answer WHERE id=18 OR id=19;";
const query: QueryOptions = {query: queryString, parameters: []};
dbhandler.executeQuery(query, (err: Error, result?: QueryResult) => {
......@@ -573,8 +573,8 @@ describe("Read and Write on Database", () => {
dbhandler.writeFormAnswer(formAnswer, (err: Error, formAnswerResult: FormAnswer) => {
expect(err).to.be.a("null");
expect(formAnswerResult.id).to.be.equal(7);
let inputAnswerId: number = 15;
expect(formAnswerResult.id).to.be.equal(8);
let inputAnswerId: number = 18;
for (const key of Object.keys(formAnswerResult.inputAnswers)){
for (const inputAnswer of formAnswerResult.inputAnswers[parseInt(key, 10)]){
expect(inputAnswer.id).to.be.equal(inputAnswerId);
......
/*
* form-creator-api. RESTful API to manage and answer forms.
* Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
* Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR
*
* This file is part of form-creator-api.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* form-creator-api. RESTful API to manage and answer forms.
* Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
* Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR
*
* This file is part of form-creator-api.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Parameters used to create a dictionary to uses as an object of erro, returning all validations erros just once
*/
export interface ValidationDict {
/** A key is a identifier of a Input instance, and the string is all invalid messages */
[key: number]: string;
}
/**
* ValidationError: Extends Error class
* Has a dict that allow us to know which answer is invalide
*/
export class ValidationError extends Error{
/** A dict that allows user to know which Input Answer is invalid. */
public readonly validationDict: ValidationDict;
constructor(validationDict: ValidationDict, ...params: any[]){
super(...params);
this.validationDict = validationDict;
}
}
......@@ -20,84 +20,168 @@
*/
import { expect } from "chai";
import { Input, InputOptions, Validation } from "../core/input";
import { Form } from "../core/form";
import { FormAnswer, FormAnswerOptions } from "../core/formAnswer";
import { InputAnswerOptions, InputAnswerOptionsDict } from "../core/inputAnswer";
import { configs } from "./config";
import { DbHandler } from "./dbHandler";
import { EnumHandler, InputType, ValidationType } from "./enumHandler";
import { OptHandler } from "./optHandler";
import {ValidationHandler } from "./validationHandler";
import { ValidationHandler } from "./validationHandler";
describe("Validation Handler", () => {
it("should test when Input is mandatory", () => {
const dbhandler = new DbHandler(configs.poolconfig);
const inputObj: InputOptions = {
placement: 0
, description: "Description"
, question: "Question"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MANDATORY, arguments: [] }
]
it("should test when Input has a minimum char number", (done) => {
const inputAnswersOpt1: InputAnswerOptions = {
idInput: 4
, placement: 0
, value: "MIN 8"
};
const inputAnswersOpt2: InputAnswerOptions = {
idInput: 5
, placement: 0
, value: "Answer to Question 2 Form 2"
};
const inputAnswerOptionsDict: InputAnswerOptionsDict = {
4: [inputAnswersOpt1]
, 5: [inputAnswersOpt2]
};
const input = new Input (OptHandler.input(inputObj));
expect(ValidationHandler.validateInput(input, "FOOL")).to.be.true;
expect(ValidationHandler.validateInput(input, "")).to.be.false;
expect(ValidationHandler.validateInput(input, null)).to.be.false;
expect(ValidationHandler.validateInput(input, undefined)).to.be.false;
const data: Date = new Date(2019, 6, 4);
dbhandler.readForm(2, (error: Error, form: Form) => {
const formAnswerOptions: FormAnswerOptions = {
form
, timestamp: data
, inputsAnswerOptions: inputAnswerOptionsDict
};
const formAnswer = new FormAnswer(OptHandler.formAnswer(formAnswerOptions));
try {
ValidationHandler.validateFormAnswer(formAnswer);
} catch (e) {
expect(e.validationDict["4"]).to.be.equal("Input answer must be greater than 8");
expect(e.validationDict["5"]).to.be.a("undefined");
}
done();
});
});
it("should test when Input has a regex", () => {
const inputObj: InputOptions = {
placement: 0
, description: "Description"
, question: "Question"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.REGEX, arguments: ["\\d{5}-\\d{3}"] }
]
it("should test when Input is mandatory", (done) => {
const inputAnswersOpt1: InputAnswerOptions = {
idInput: 4
, placement: 0
, value: "Answer to Question 1 Form 2"
};
const inputAnswersOpt2: InputAnswerOptions = {
idInput: 5
, placement: 0
, value: ""
};
const inputAnswerOptionsDict: InputAnswerOptionsDict = {
4: [inputAnswersOpt1]
, 5: [inputAnswersOpt2]
};
const input = new Input (OptHandler.input(inputObj));
expect(ValidationHandler.validateInput(input, "88888-888")).to.be.true;
expect(ValidationHandler.validateInput(input, "88888-88")).to.be.false;
expect(ValidationHandler.validateInput(input, "")).to.be.false;
expect(ValidationHandler.validateInput(input, null)).to.be.false;
expect(ValidationHandler.validateInput(input, undefined)).to.be.false;
const data: Date = new Date(2019, 6, 4);
dbhandler.readForm(2, (error: Error, form: Form) => {
const formAnswerOptions: FormAnswerOptions = {
form
, timestamp: data
, inputsAnswerOptions: inputAnswerOptionsDict
};
const formAnswer = new FormAnswer(OptHandler.formAnswer(formAnswerOptions));
try {
ValidationHandler.validateFormAnswer(formAnswer);
} catch (e) {
expect(e.validationDict["4"]).to.be.a("undefined");
expect(e.validationDict["5"]).to.be.equal("Input answer is mandatory");
}
done();
});
});
it("should test when Input has a minimum char number", () => {
const inputObj: InputOptions = {
placement: 0
, description: "Description"
, question: "Question"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MINCHAR, arguments: ["5"] }
]
it("should test when Input has a maximum char number", (done) => {
const inputAnswersOpt1: InputAnswerOptions = {
idInput: 1
, placement: 0
, value: "Answer to Question 1 Form 1"
};
const inputAnswersOpt2: InputAnswerOptions = {
idInput: 2
, placement: 0
, value: "12345-000"
};
const inputAnswersOpt3: InputAnswerOptions = {
idInput: 3
, placement: 0
, value: "MORE THEN 10 CHAR"
};
const input = new Input (OptHandler.input(inputObj));
const inputAnswerOptionsDict: InputAnswerOptionsDict = {
1: [inputAnswersOpt1]
, 2: [inputAnswersOpt2]
, 3: [inputAnswersOpt3]
};
const data: Date = new Date(2019, 6, 4);
dbhandler.readForm(1, (error: Error, form: Form) => {
const formAnswerOptions: FormAnswerOptions = {
form
, timestamp: data
, inputsAnswerOptions: inputAnswerOptionsDict
};
const formAnswer = new FormAnswer(OptHandler.formAnswer(formAnswerOptions));
try {
ValidationHandler.validateFormAnswer(formAnswer);
} catch (e) {
expect(e.validationDict["1"]).to.be.a("undefined");
expect(e.validationDict["2"]).to.be.a("undefined");
expect(e.validationDict["3"]).to.be.equal("Input answer must be lower than 10");
}
done();
});
expect(ValidationHandler.validateInput(input, "12345")).to.be.true;
expect(ValidationHandler.validateInput(input, "123456")).to.be.true;
expect(ValidationHandler.validateInput(input, "1234")).to.be.false;
expect(ValidationHandler.validateInput(input, "")).to.be.false;
expect(ValidationHandler.validateInput(input, null)).to.be.false;
expect(ValidationHandler.validateInput(input, undefined)).to.be.false;
});
it("should test when Input has a maximum char number", () => {
const inputObj: InputOptions = {
placement: 0
, description: "Description"
, question: "Question"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MAXCHAR, arguments: ["5"] }
]
it("should test when Input has a RegEx", (done) => {
const inputAnswersOpt1: InputAnswerOptions = {
idInput: 1
, placement: 0
, value: "Answer to Question 1 Form 1"
};
const inputAnswersOpt2: InputAnswerOptions = {
idInput: 2
, placement: 0
, value: "12aa345-000"
};
const input = new Input (OptHandler.input(inputObj));
expect(ValidationHandler.validateInput(input, "1234")).to.be.true;
expect(ValidationHandler.validateInput(input, "12345")).to.be.true;
expect(ValidationHandler.validateInput(input, "")).to.be.true;
expect(ValidationHandler.validateInput(input, "123456")).to.be.false;
expect(ValidationHandler.validateInput(input, null)).to.be.false;
expect(ValidationHandler.validateInput(input, undefined)).to.be.false;
const inputAnswersOpt3: InputAnswerOptions = {
idInput: 3
, placement: 0
, value: "MAXCHAR 10"
};
const inputAnswerOptionsDict: InputAnswerOptionsDict = {
1: [inputAnswersOpt1]
, 2: [inputAnswersOpt2]
, 3: [inputAnswersOpt3]
};
const data: Date = new Date(2019, 6, 4);
dbhandler.readForm(1, (error: Error, form: Form) => {
const formAnswerOptions: FormAnswerOptions = {
form
, timestamp: data
, inputsAnswerOptions: inputAnswerOptionsDict
};
const formAnswer = new FormAnswer(OptHandler.formAnswer(formAnswerOptions));
try {
ValidationHandler.validateFormAnswer(formAnswer);
} catch (e) {
expect(e.validationDict["1"]).to.be.a("undefined");
expect(e.validationDict["3"]).to.be.a("undefined");
expect(e.validationDict["2"]).to.be.equal("RegEx do not match");
}
done();
});
});
});
......@@ -19,8 +19,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { FormAnswer } from "../core/formAnswer";
import { Input } from "../core/input";
import { ValidationType } from "./enumHandler";
import { ValidationDict, ValidationError } from "./validationError";
/**
* Validation's handler. Manage parse validation through the project.
......@@ -71,30 +73,54 @@ export class ValidationHandler {
* Validate if answer has minimum number of chars
* @param input - Input to validate answer.
* @param answer - Answer of input
* @returns - true if answer is valid according a given Input.
* @returns - A string with all errors.
*/
public static validateInput(input: Input, answer: string): boolean{
let result: boolean = true;
private static validateInput(input: Input, answer: string): string{
const errors: string[] = [];
for ( const validation of input.validation){
switch (validation.type) {
case ValidationType.REGEX:
result = result && this.validateByRegex(answer, validation.arguments[0]);
if (!this.validateByRegex(answer, validation.arguments[0])){
errors.push("RegEx do not match");
}
break;
case ValidationType.MANDATORY:
result = result && this.validateMandatory(answer);
if (!(this.validateMandatory(answer))){
errors.push("Input answer is mandatory");
}
break;
case ValidationType.MAXCHAR:
result = result && this.validateMaxChar(answer, validation.arguments[0]);
if (!(this.validateMaxChar(answer, validation.arguments[0]))){
errors.push("Input answer must be lower than " + validation.arguments[0]);
}
break;
case ValidationType.MINCHAR:
result = result && this.validateMinChar(answer, validation.arguments[0]);
if (!(this.validateMinChar(answer, validation.arguments[0]))){
errors.push("Input answer must be greater than " + validation.arguments[0]);
}
break;
}
}
return errors.join(";");
}
public static validateFormAnswer(formAnswer: FormAnswer): void{
const errorsDict: ValidationDict = {};
for ( const input of formAnswer.form.inputs){
for (const answer of formAnswer.inputAnswers[input.id]){
const error: string = this.validateInput(input, answer.value);
if (error !== "" && error !== undefined){
errorsDict[input.id] = error;
}
}
}
return result;
if ( Object.keys(errorsDict).length > 0){
throw new ValidationError(errorsDict, "Validation Error");
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment