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

Issue #19: Create OptHandler

Signed-off-by: Matheus Horstmann's avatarMatheus Horstmann <mch15@inf.ufpr.br>
parent 763d5a0a
......@@ -4,12 +4,27 @@ 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.12 - 25-04-2019
### Added
- OptHandler to standardize constructors from Forms and Inputs #19 (Horstmann)
- InputOptions interface on class Input #19 (Horstmann)
- FormOptions interface on class Form #19 (Horstmann)
### Changed
- Tests to adapt to new standard of options
- dbHandler's readInputValidationWithInputId method to return a InputOptions instead of an input
- Tests to adapt to new standard of options
- ErrorHandler to add a new error message
## 0.0.11 - 17-04-2019
### Added
- ErrorHandler to standardize errors message through the project #17 (Horstmann)
### Changed
- TestHandler documentation title
- dbHandler tests to use ErrorHandler #18 (Horstmann)
## 0.0.10 - 16-04-2019
### Added
- TestHandler to test form and inputs #18 (Horstmann)
......@@ -21,20 +36,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ValidationHandler to receive a string as size instead number as validation arguments
- ValidationHandler to cast size to number
- ValidationHandler tests to use string instead of number as validation arguments
## 0.0.9 - 10-04-2019
### Added
- Method read in Form controller to get a Form
## 0.0.8 - 10-04-2019
### Changed
- main.ts to remove more dummie class
- dbHandler to include method listForms
### Added
- Form controller and method to list all forms
## 0.0.7 - 10-04-2019
### Changed
- main.ts to include dbHandler Middleware and remove dummie class
### Added
- Create dbHandler Middleware to be able to access by routes #15 (Horstmann)
## 0.0.6 - 01-04-2019
### Changed
- Input class to match with database model (Add id and description) (Horstmann)
......@@ -45,6 +68,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Create readInput method to read input from database #7 (Horstmann)
- Create writeForm method to insert form into database (Horstmann)
- Comments to coverage ignore errors that are not reached on tests.
## 0.0.5 - 19-03-2019
### Changed
- Remove tslint-stylish from package.json, package is deprecated (Horstmann)
......
{
"name": "form-creator-api",
"version": "0.0.11",
"version": "0.0.12",
"description": "RESTful API used to manage and answer forms.",
"main": "index.js",
"scripts": {
......
......@@ -24,8 +24,9 @@
import * as server from "../../main";
import { EnumHandler,InputType, ValidationType } from "../../utils/enumHandler";
import { TestHandler } from "../../utils/testHandler";
import { Form } from "../../core/form";
import { Input, Validation } from "../../core/input";
import { OptHandler } from "../../utils/optHandler";
import { Form, FormOptions } from "../../core/form";
import { Input, InputOptions, Validation } from "../../core/input";
describe("API data controller", () => {
......@@ -59,22 +60,54 @@
.get("/forms/1")
.expect(200)
.expect((res: any) => {
expect(res.body).to.be.an("object");
const inputs: Input[] = [];
for( let tmpInput of res.body.inputs ){
let validation: Validation[] = [];
for( let tmpValidation of tmpInput.validation ){
validation.push({type: tmpValidation.type, arguments: tmpValidation.arguments})
}
inputs.push( new Input (tmpInput.placement, tmpInput.description, tmpInput.question, tmpInput.type, validation, tmpInput.id));
}
const form: Form = new Form(res.body.title, res.body.description, inputs, res.body.id);
TestHandler.testForm(form, new Form("Form Title 1", "Form Description 1"
,[ new Input (0, "Description Question 1 Form 1", "Question 1 Form 1", InputType.TEXT, [], 1)
,new Input (1, "Description Question 2 Form 1", "Question 2 Form 1", InputType.TEXT, [{type: ValidationType.MAXCHAR, arguments: ["10"]}, {type: ValidationType.MINCHAR, arguments: ["2"]}], 3)
,new Input (2, "Description Question 3 Form 1", "Question 3 Form 1", InputType.TEXT, [{type: ValidationType.REGEX, arguments: ["\\d{5}-\\d{3}"]}, {type: ValidationType.MANDATORY, arguments: []}], 2)]
, 1));
expect(res.body).to.be.an("object");
const form: Form = new Form(OptHandler.form(res.body));
const inputObj1: InputOptions = {
placement: 0
, description: "Description Question 1 Form 1"
, question: "Question 1 Form 1"
, type: InputType.TEXT
, validation: []
, id: 1
};
const inputObj2: InputOptions = {
placement: 1
, description: "Description Question 2 Form 1"
, question: "Question 2 Form 1"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MAXCHAR, arguments: ["10"] }
, { type: ValidationType.MINCHAR, arguments: ["2"] }
]
, id: 3
};
const inputObj3: InputOptions = {
placement: 2
, description: "Description Question 3 Form 1"
, question: "Question 3 Form 1"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.REGEX, arguments: ["\\d{5}-\\d{3}"] }
, { type: ValidationType.MANDATORY, arguments: [] }
]
, id: 2
};
const formObj: FormOptions = {
id: 1
, title: "Form Title 1"
, description: "Form Description 1"
, inputs:[
inputObj1
, inputObj2
, inputObj3
]
};
TestHandler.testForm(form, new Form( OptHandler.form(formObj)));
})
.end(done);
......
......@@ -19,12 +19,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Input } from "./input";
/**
* Form Class to manage project's forms
*/
import { OptHandler } from "../utils/optHandler";
import { Input, InputOptions } from "./input";
export class Form {
/** Parameters used to create a form object. */
export interface FormOptions {
/** Unique identifier of a Form instance */
id?: number;
/** Form's title. An human-understandable identifier. Not unique */
title: string;
/** Form Description, as propose */
description: string;
/** Array of input. containing question */
inputs: InputOptions[];
}
/**
* Form Class to manage project's forms
*/
export class Form {
/** Unique identifier of a Form instance */
public readonly id: number;
/** Form's title. An human-understandable identifier. Not unique */
......@@ -41,11 +56,13 @@
* @param inputs - Forms's question
* @param id - Form's identifier
*/
constructor(title: string, description: string, inputs: Input[], id?: number) {
this.id = id ? id : null;
this.title = title;
this.description = description;
this.inputs = inputs;
constructor(options: FormOptions) {
this.id = options.id ? options.id : null;
this.title = options.title;
this.description = options.description;
this.inputs = options.inputs.map((i: any) => {
return new Input(OptHandler.input(i));
});
}
}
......@@ -21,6 +21,21 @@
import { InputType, ValidationType } from "../utils/enumHandler";
/** Parameters used to create a input object. */
export interface InputOptions {
/** Unique identifier of a Input instance. */
id?: number;
/** Place where input should be in the form. */
placement: number;
/** Input's Description */
description: string;
/** Question of input */
question: string;
/** Type of input */
type: InputType;
/** Array contain all input's validation */
validation: Validation[];
}
/** Validation contains the type of it, and n arguments to validate if necessary */
export interface Validation {
......@@ -58,13 +73,13 @@ export class Input {
* @param id - Input identifier
*/
constructor(placement: number, description: string, question: string, type: InputType, validation: Validation[], id?: number) {
this.id = id ? id : null;
this.placement = placement;
this.description = description;
this.question = question;
this.type = type;
this.validation = validation;
constructor(options: InputOptions) {
this.id = options.id ? options.id : null;
this.placement = options.placement;
this.description = options.description;
this.question = options.question;
this.type = options.type;
this.validation = options.validation;
}
}
......@@ -21,11 +21,12 @@
import { series } from "async";
import { expect } from "chai";
import { QueryResult } from "pg";
import { Form } from "../core/form";
import { Input, Validation } from "../core/input";
import { Form, FormOptions } from "../core/form";
import { Input, InputOptions, Validation } from "../core/input";
import { configs } from "./config";
import { DbHandler } from "./dbHandler";
import { InputType, ValidationType } from "./enumHandler";
import { OptHandler } from "./optHandler";
import { TestHandler } from "./testHandler";
describe("Database Handler", () => {
......@@ -318,11 +319,50 @@ describe("Read and Write on Database", () => {
dbhandler.readForm(1, (err: Error, form: Form) => {
expect(err).to.be.a("null");
TestHandler.testForm(form, new Form("Form Title 1", "Form Description 1"
, [ new Input (0, "Description Question 1 Form 1", "Question 1 Form 1", InputType.TEXT, [], 1)
, new Input (1, "Description Question 2 Form 1", "Question 2 Form 1", InputType.TEXT, [{type: ValidationType.MAXCHAR, arguments: ["10"]}, {type: ValidationType.MINCHAR, arguments: ["2"]}], 3)
, new Input (2, "Description Question 3 Form 1", "Question 3 Form 1", InputType.TEXT, [{type: ValidationType.REGEX, arguments: ["\\d{5}-\\d{3}"]}, {type: ValidationType.MANDATORY, arguments: []}], 2)]
, 1));
const inputObj1: InputOptions = {
placement: 0
, description: "Description Question 1 Form 1"
, question: "Question 1 Form 1"
, type: InputType.TEXT
, validation: []
, id: 1
};
const inputObj2: InputOptions = {
placement: 1
, description: "Description Question 2 Form 1"
, question: "Question 2 Form 1"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MAXCHAR, arguments: ["10"] }
, { type: ValidationType.MINCHAR, arguments: ["2"] }
]
, id: 3
};
const inputObj3: InputOptions = {
placement: 2
, description: "Description Question 3 Form 1"
, question: "Question 3 Form 1"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.REGEX, arguments: ["\\d{5}-\\d{3}"] }
, { type: ValidationType.MANDATORY, arguments: [] }
]
, id: 2
};
const formObj: FormOptions = {
id: 1
, title: "Form Title 1"
, description: "Form Description 1"
, inputs: [
OptHandler.input(inputObj1)
, OptHandler.input(inputObj2)
, OptHandler.input(inputObj3)
]
};
TestHandler.testForm(form, new Form(OptHandler.form(formObj)));
done();
});
......@@ -339,22 +379,41 @@ describe("Read and Write on Database", () => {
it("should write form", (done) => {
let validation: Validation[];
validation = [{type: ValidationType.MANDATORY, arguments: []}];
let input = new Input (1, "Description 1", "Question 1", InputType.TEXT, validation);
const inputs: Input[] = [];
inputs.push(input);
validation = [{type: ValidationType.MINCHAR, arguments: ["5"]}];
input = new Input (2, "Description 2", "Question 2", InputType.TEXT, validation);
inputs.push(input);
const form = new Form("Form Title 1", "Form Description 1", inputs);
const inputObj1: InputOptions = {
placement: 0
, description: "Description 1"
, question: "Question 1 Form 1"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MANDATORY, arguments: [] }
]
};
const inputObj2 = {
placement: 1
, description: "Description 2"
, question: "Question 2"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MINCHAR, arguments: ["5"] }
]
};
const formObj: FormOptions = {
title: "Form Title 1"
, description: "Form Description 1"
, inputs: [
OptHandler.input(inputObj1)
, OptHandler.input(inputObj2)
]
};
const form = new Form(OptHandler.form(formObj));
dbhandler.writeForm(form, (err: Error, formResult: Form) => {
expect(err).to.be.a("null");
expect(formResult.id).to.be.equal(4);
let inputId: number = 8;
for (input of formResult.inputs){
for (const input of formResult.inputs){
expect(input.id).to.be.equal(inputId);
inputId++;
}
......
......@@ -21,10 +21,11 @@
import { eachOfSeries, eachSeries , map, waterfall } from "async";
import { Pool, PoolConfig, QueryResult } from "pg";
import { Form } from "../core/form";
import { Input, Validation } from "../core/input";
import { Form, FormOptions } from "../core/form";
import { Input, InputOptions, Validation } from "../core/input";
import { EnumHandler, InputType, ValidationType } from "./enumHandler";
import { ErrorHandler} from "./errorHandler";
import { OptHandler } from "./optHandler";
/**
* Class of the SGBD from the Form Creator Api perspective. Used to
......@@ -108,15 +109,22 @@ export class DbHandler {
}
for (const row of result.rows){
const inputs: Input[] = [];
const formTmp = new Form (
row["title"],
row["description"],
inputs,
row["id"]
);
forms.push(formTmp);
const formObj: FormOptions = {
id: row["id"]
, title: row["title"]
, description: row["description"]
, inputs: []
};
let formTmp: Form;
try{
formTmp = new Form ( OptHandler.form(formObj));
}
catch (e){
cb(e);
return;
}
forms.push(formTmp);
}
cb(err, forms);
});
......@@ -145,14 +153,24 @@ export class DbHandler {
callback(ErrorHandler.badIdAmount(result.rowCount));
return;
}
this.readInputWithFormId(id, (error: Error, inputs: Input[]) => {
const formTmp = new Form (
result.rows[0]["title"],
result.rows[0]["description"],
inputs,
result.rows[0]["id"]
);
callback(error, formTmp);
this.readInputWithFormId(id, (error: Error, inputsResult: Input[]) => {
const formObj: FormOptions = {
id: result.rows[0]["id"]
, title: result.rows[0]["title"]
, description: result.rows[0]["description"]
, inputs: inputsResult
};
let formTmp: Form;
try{
formTmp = new Form ( OptHandler.form(formObj));
}
catch (e){
callback(e);
return;
}
callback(error, formTmp);
});
},
(form: Form, callback: (err: Error, form?: Form) => void) => {
......@@ -183,22 +201,32 @@ export class DbHandler {
* @param cb.err - Error information when the method fails.
* @param cb.inputs - Input array or an empty list if there is no input linked to form.
*/
private readInputWithFormId(id: number, cb: (err: Error, inputs?: Input[]) => void){
private readInputWithFormId(id: number, cb: (err: Error, inputs?: InputOptions[]) => void){
const query: string = "SELECT id, placement, input_type, question, description FROM input WHERE id_form=" + id + ";";
this.executeQuery(query, (err: Error, result?: QueryResult) => {
map(result.rows, (innerResult, callback) => {
this.readInputValidationWithInputId(innerResult["id"], (error: Error, validation: Validation[]) => {
const inputTmp = new Input (
innerResult["placement"],
innerResult["description"],
innerResult["question"],
EnumHandler.parseInputType(innerResult["input_type"]),
validation,
innerResult["id"]);
this.readInputValidationWithInputId(innerResult["id"], (error: Error, validationResult: Validation[]) => {
const inputObj: InputOptions = {
id: innerResult["id"]
, placement: innerResult["placement"]
, description: innerResult["description"]
, question: innerResult["question"]
, validation: validationResult
, type: EnumHandler.parseInputType(innerResult["input_type"])
};
let inputTmp: InputOptions;
try{
inputTmp = OptHandler.input(inputObj);
}
catch (e){
callback(e);
return;
}
callback(error, inputTmp);
});
}, (errors, inputs: Input[]) => {
}, (errors, inputs: InputOptions[]) => {
if (errors){
this.rollback( (error: Error, results?: QueryResult) => {
......@@ -208,7 +236,7 @@ export class DbHandler {
return;
}
const sortedInputs: Input[] = DbHandler.sortByPlacement(inputs);
const sortedInputs: InputOptions[] = DbHandler.sortByPlacement(inputs);
cb(errors, sortedInputs);
});
......
......@@ -41,4 +41,13 @@
return new Error(objectName + " not inserted");
}
/**
* Return an error when a object is not found.
* @param objectName - Name of object, usually the name of the table.
* @returns - An error instance with correct message.
*/
public static notFound(objectName: string): Error{
return new Error("The dataType named '" + objectName + "' was not found");
}
}
This diff is collapsed.
/*
* 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 { FormOptions } from "../core/form";
import { Input, InputOptions, Validation } from "../core/input";
import { InputType} from "./enumHandler";
import { ErrorHandler} from "./errorHandler";
/**
* OptHandler to handle an object and transform into a Classoptions to be used in Class's constructor
*/
export class OptHandler {
/**
* Return an FormOptions instance with a parsed object, The main objective is parse any error previously
* @param obj - object that should be parsed.
* @returns - An FormOptions instance.
*/
public static form(obj: any): FormOptions{
if (obj.title === undefined ){
throw ErrorHandler.notFound("Form title");
}
if (obj.description === undefined){
throw ErrorHandler.notFound("Form description");
}
if (obj.inputs === undefined || !(obj.inputs instanceof Array)){
throw ErrorHandler.notFound("Form inputs");
}
const option: FormOptions = {
title: obj.title,
description: obj.description,
id: obj.id,
inputs: obj.inputs.map((i: any) => OptHandler.input(i))
};
return option;
}
/**
* Return an InputOptions instance with a parsed object, The main objective is parse any error previously
* @param obj - object that should be parsed.
* @returns - An InputOptions instance.
*/
public static input(obj: any): InputOptions{
if (obj.placement === undefined){
throw ErrorHandler.notFound("Input placement");
}
if (obj.description === undefined){
throw ErrorHandler.notFound("Input description");
}
if (obj.question === undefined){
throw ErrorHandler.notFound("Input question");
}
if (obj.type === undefined){
throw ErrorHandler.notFound("Input type");
}
if (obj.validation === undefined || !(obj.validation instanceof Array)){
throw ErrorHandler.notFound("Input validation");
}
const option: InputOptions = {
id: obj.id,
placement: obj.placement,
description: obj.description,
question: obj.question,
type: obj.type,
validation: obj.validation.map((v: any) => {
return {type: v.type, arguments: v.arguments};
})
};
// for ( const tmpValidation of obj.validation ){
// option.validation.push({type: tmpValidation.type, arguments: tmpValidation.arguments});
// }
return option;
}
}
......@@ -20,15 +20,25 @@
*/
import { expect } from "chai";
import { Input, Validation } from "../core/input";
import { Input, InputOptions, Validation } from "../core/input";
import { EnumHandler, InputType, ValidationType } from "./enumHandler";
import { OptHandler } from "./optHandler";
import {ValidationHandler } from "./validationHandler";
describe("Validation Handler", () => {
it("should test when Input is mandatory", () => {
const validation: Validation[] = [{type: ValidationType.MANDATORY, arguments: []}];
const input = new Input (1, "Description", "Question", InputType.TEXT, validation);
const inputObj: InputOptions = {
placement: 0
, description: "Description"
, question: "Question"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MANDATORY, arguments: [] }
]
};
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;
......@@ -36,9 +46,16 @@ describe("Validation Handler", () => {
});
it("should test when Input has a regex", () => {
const validation: Validation[] = [{type: ValidationType.REGEX, arguments: ["\\d{5}-\\d{3}"]}];
const input = new Input (1, "Description", "Question", InputType.TEXT, validation);
const inputObj: InputOptions = {
placement: 0
, description: "Description"
, question: "Question"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.REGEX, arguments: ["\\d{5}-\\d{3}"] }
]
};
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;
......@@ -47,8 +64,16 @@ describe("Validation Handler", () => {
});
it("should test when Input has a minimum char number", () => {
const validation: Validation[] = [{type: ValidationType.MINCHAR, arguments: ["5"]}];
const input = new Input (1, "Description", "Question", InputType.TEXT, validation);
const inputObj: InputOptions = {
placement: 0
, description: "Description"
, question: "Question"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MINCHAR, arguments: ["5"] }
]
};
const input = new Input (OptHandler.input(inputObj));
expect(ValidationHandler.validateInput(input, "12345")).to.be.true;
expect(ValidationHandler.validateInput(input, "123456")).to.be.true;
......@@ -58,9 +83,16 @@ describe("Validation Handler", () => {
expect(ValidationHandler.validateInput(input, undefined)).to.be.false;
});
it("should test when Input has a maximum char number", () => {
const validation: Validation[] = [{type: ValidationType.MAXCHAR, arguments: ["5"]}];
const input = new Input (1, "Description", "Question", InputType.TEXT, validation);
const inputObj: InputOptions = {
placement: 0
, description: "Description"
, question: "Question"
, type: InputType.TEXT
, validation: [
{ type: ValidationType.MAXCHAR, arguments: ["5"] }
]
};
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;
......