Commit 3b0a5eec authored by rafaelatc3sl's avatar rafaelatc3sl

Issue #92: Improve test coverage

Signed-off-by: 's avatarrafaelatc3sl <rpd17@c3sl>
parent 72c00bdc
Pipeline #18958 passed with stages
in 1 minute and 7 seconds
......@@ -24,22 +24,11 @@ stages:
- deploy
run_test_postgres:
run_test:
stage: test
script:
- yarn install --frozen-lockfile --silent --non-interactive
- mv config/ci_postgres_test.env.example config/test.env
- yarn test
- yarn run lint
tags:
- node
- postgres
run_test_monet:
stage: test
script:
- yarn install --frozen-lockfile --silent --non-interactive
- mv config/ci_monet_test.env.example config/test.env
- mv config/ci_database_test.env.example config/test.env
- yarn test
- yarn run lint
tags:
......
PORT=3000
BLENDB_N_DB=2
BLENDB_DB0_USER=runner
BLENDB_DB0_NAME=blendb_fixture
BLENDB_DB0_PASSWORD=
BLENDB_DB0_HOST=postgres
BLENDB_DB0_PORT=5432
BLENDB_DB0_ADAPTER=postgres
BLENDB_DB1_USER=monetdb
BLENDB_DB1_NAME=blendb_fixture
BLENDB_DB1_PASSWORD=monetdb
BLENDB_DB1_HOST=monet
BLENDB_DB1_PORT=50000
BLENDB_DB1_ADAPTER=monet
BLENDB_SCHEMA_FILE=config/ci_test.yaml.example
BLENDB_DB_USER=monetdb
BLENDB_DB_NAME=blendb_fixture
BLENDB_DB_PASSWORD=monetdb
BLENDB_DB_HOST=monet
BLENDB_DB_PORT=50000
BLENDB_ADAPTER=monet
BLENDB_SCHEMA_FILE=config/ci_test.yaml.example
PORT=3000
BLENDB_DB_USER=runner
BLENDB_DB_NAME=blendb_fixture
BLENDB_DB_PASSWORD=
BLENDB_DB_HOST=postgres
BLENDB_DB_PORT=5432
BLENDB_ADAPTER=postgres
BLENDB_SCHEMA_FILE=config/ci_test.yaml.example
PORT=3000
......@@ -46,6 +46,7 @@ enumTypes:
sources:
links:
- config/market_sources.yaml.example
- config/source_test_only.yaml.example
obj:
-
name: "Seller"
......
......@@ -19,27 +19,27 @@
-
name: "dim:product:name"
dataType: "string"
description: "Name of the product from market"
description: "Name of the product from market"
-
name: "dim:product:validity"
dataType: "date"
description: "Validity of the product from market"
-
-
name: "dim:client:name"
dataType: "string"
description: "Name of the client from market"
-
-
name: "dim:client:cpf"
dataType: "string"
description: "CPF of the client from market"
-
name: "dim:sell:registered"
dataType: "boolean"
description: "Check if the client is registered"
description: "Check if the client is registered"
-
name: "dim:seller:id"
dataType: "integer"
description: "id of the seller from market"
description: "id of the seller from market"
-
name: "dim:client:id"
dataType: "integer"
......@@ -47,21 +47,39 @@
-
name: "dim:sell:datein"
dataType: "date"
description: "Date of the sell was realized"
description: "Date of the sell was realized"
-
name: "dim:buyout:datein"
dataType: "date"
description: "Date of the buyout was realized"
description: "Date of the buyout was realized"
-
name: "dim:product:id"
dataType: "integer"
description: "id of the product from market"
description: "id of the product from market"
-
name: "dim:provider:name"
dataType: "string"
description: "Name of the provider from market"
description: "Name of the provider from market"
-
name: "dim:provider:id"
dataType: "string"
description: "id of the provider from market"
description: "id of the provider from market"
-
name: "dim:buyout:day"
dataType: "integer"
parent: "dim:buyout:datein"
relation: "day"
description: "Date of the buyout was realized"
-
name: "dim:buyout:month"
dataType: "integer"
parent: "dim:buyout:datein"
relation: "month"
description: "Date of the buyout was realized"
-
name: "dim:buyout:year"
dataType: "integer"
parent: "dim:buyout:datein"
relation: year
description: "Date of the buyout was realized"
......@@ -13,5 +13,4 @@
- "vacation"
- "sick leave"
- "maternity leave"
- "stand by"
......@@ -79,3 +79,12 @@
- "met:seller:max:age"
clauses:
- "dim:seller:status==active"
-
alias: "view:SellerStatus"
data: "test/postgres/fixtures/sellerstatus.json"
origin: false
aliasAsName: true
metrics:
- "met:seller:count:age"
dimensions:
- "dim:seller:status"
-
name: "test"
description:
fields:
-
name: "noDescriptionString"
description:
dataType: "string"
[
{"met:seller:count:age" : 4, "dim:seller:status" : "active"},
{"met:seller:count:age" : 1, "dim:seller:status" : "sick leave"}
]
......@@ -142,14 +142,6 @@ export class MonetAdapter extends SQLAdapter {
pool.close();
}
/**
* Materialize a given view.
* @param view - View to be materialized.
*/
public materializeView(view: View): boolean {
return false;
}
/**
* Asynchronously insert one register into a given Source.
* @param source - Insertion "location".
......
This diff is collapsed.
......@@ -86,14 +86,6 @@ export class PostgresAdapter extends SQLAdapter {
this.executeQuery(query, cb);
}
/**
* Materialize a given view.
* @param view - View to be materialized.
*/
public materializeView(view: View): boolean {
return false;
}
/**
* Cast BlenDB data types to be used in PostgreSQL queries.
* @param quotedValue - SQL query attribute wrapped by quotes.
......
......@@ -22,53 +22,80 @@ import * as request from "supertest";
import { expect } from "chai";
import * as server from "../../main";
import { Adapter } from "../../core/adapter";
import { ConfigParser } from "../../util/configParser";
import { ConfigParser, ParsedConfig } from "../../util/configParser";
import { Fixture as FixPostgres } from "../../../test/postgres/fixture";
import { Fixture as FixMonet } from "../../../test/monet/fixture";
import { MonetAdapter, MonetConfig } from "../../adapter/monet";
import { PostgresAdapter } from "../../adapter/postgres";
import { eachOf } from "async";
import { Source } from "../../core/source";
describe("API collect controller", () => {
let adapter: Adapter[] = [];
let fixture;
let config: ParsedConfig;
function loadDb(db: string, index: number, cb: (err: any, result: Adapter) => void): void {
let adapter: Adapter;
if (db === "postgres") {
fixture = new FixPostgres(config.connections[index]);
fixture.loadSource(config.sources, (err) => {
if (err) {
throw err;
}
adapter = new PostgresAdapter(config.connections[index]);
cb(null, adapter);
});
}
else if (db === "monet") {
fixture = new FixMonet(config.connections[index]);
fixture.loadSource(config.sources, (err) => {
if (err) {
throw err;
}
let parsedConfig: MonetConfig = {
user: config.connections[index].user,
dbname: config.connections[index].database,
password: config.connections[index].password,
host: config.connections[index].host,
port: config.connections[index].port
};
adapter = new MonetAdapter(parsedConfig);
cb(null, adapter);
});
}
else{
cb("invalid adapter", null);
}
}
describe("API collect controller", () => {
// Initializing
let config: any;
let adapter: Adapter;
let fixture;
before(function (done): void {
// Arrow function not used to get acces to this and skip the test
// Arrow function not used to get acces to this and skip the test
const configPath = process.env.BLENDB_SCHEMA_FILE;
config = ConfigParser.parse(configPath);
if (config.adapter === "postgres") {
fixture = new FixPostgres(config.connection);
fixture.loadSource(config.sources, (err) => {
if (err) {
throw err;
config = ConfigParser.parse(configPath);
eachOf(config.adapters , function(database, key: number, callback) {
loadDb(database, key, function(err, result ) {
if (err){
return callback(err);
}
adapter = new PostgresAdapter(config.connection);
done();
});
}
else if (config.adapter === "monet") {
fixture = new FixMonet(config.connection);
fixture.loadSource(config.sources, (err) => {
if (err) {
throw err;
else{
adapter[key] = result;
}
let parsedConfig: MonetConfig = {
user: config.connection.user,
dbname: config.connection.database,
password: config.connection.password,
host: config.connection.host,
port: config.connection.port
};
adapter = new MonetAdapter(parsedConfig);
done();
callback();
});
}
else {
this.skip();
}
}, function (err){
if (err){
this.skip();
}
else{
done();
}
});
});
it("should respond 500 when req.params.class does not exist on Sources", (done) => {
request(server)
.post("/v1/collect/thisisjustatest")
......
......@@ -110,7 +110,7 @@ export class CollectCtrl {
}else{
return false;
}
}
}
}
};
......@@ -155,16 +155,11 @@ export class CollectCtrl {
"The value '" + data[i] + "' from '" + fields[i].name +
"' isn't listed on " + fields[i].enumType);
}
}
else if (fields[i].dataType !== DataType.NONE){
// check if it's a valid datatype from query
if (!validador[EnumHandler.stringfyDataType(fields[i].dataType)](data[i]) === true){
}else if (!validador[EnumHandler.stringfyDataType(fields[i].dataType)](data[i]) === true){
throw new Error(
"The value '" + data[i] + "' from '" + fields[i].name +
"' isn't a type " + [EnumHandler.stringfyDataType(fields[i].dataType)]);
}
}
}
}
}
......
......@@ -192,7 +192,6 @@ describe("API data controller", () => {
})
.end(done);
});
it("should respond 200 and get some data, sorted", (done) => {
let query = parseQuery(tests.clausal);
query.sort = "dim:product:id,met:product:avg:pricein";
......@@ -218,5 +217,94 @@ describe("API data controller", () => {
})
.end(done);
});
it("should respond 200 and get some data, using filters with GREATER", (done) => {
let query = parseQuery(tests.clausal);
query.filters = "dim:product:id>55";
request(server)
.get("/v1/data")
.query(query)
.expect(200)
.expect((res: any) => {
let result = res.body;
expect(result).to.be.an("array");
expect(result).to.have.length(2);
expect(result[0]).to.be.an("object");
let keys: string[] = [];
keys = keys.concat(tests.clausal.metrics.map((item) => item.name));
keys = keys.concat(tests.clausal.dimensions.map((item) => item.name));
result.forEach((row: any) => {
expect(row).to.be.an("object");
expect(row).to.have.all.keys(keys);
});
})
.end(done);
});
it("should respond 200 and get some data, using filters with LOWEREQ", (done) => {
let query = parseQuery(tests.clausal);
query.filters = "dim:product:id<=55";
request(server)
.get("/v1/data")
.query(query)
.expect(200)
.expect((res: any) => {
let result = res.body;
expect(result).to.be.an("array");
expect(result).to.have.length(3);
expect(result[0]).to.be.an("object");
let keys: string[] = [];
keys = keys.concat(tests.clausal.metrics.map((item) => item.name));
keys = keys.concat(tests.clausal.dimensions.map((item) => item.name));
result.forEach((row: any) => {
expect(row).to.be.an("object");
expect(row).to.have.all.keys(keys);
});
})
.end(done);
});
it("should respond 200 and get some data, using filters with default", (done) => {
let query = parseQuery(tests.seller);
query.filters = "dim:sell:registered==false";
request(server)
.get("/v1/data")
.query(query)
.expect(200)
.expect((res: any) => {
let result = res.body;
expect(result).to.be.an("array");
expect(result).to.have.length(1);
expect(result[0]).to.be.an("object");
let keys: string[] = [];
keys = keys.concat(tests.seller.metrics.map((item) => item.name));
keys = keys.concat(tests.seller.dimensions.map((item) => item.name));
result.forEach((row: any) => {
expect(row).to.be.an("object");
expect(row).to.have.all.keys(keys);
});
})
.end(done);
});
it("should respond 200 and get some data, using filters with default", (done) => {
let query = parseQuery(tests.expensive);
query.sort = "met:product:max:pricein"
request(server)
.get("/v1/data")
.query(query)
.expect(200)
.expect((res: any) => {
let result = res.body;
expect(result).to.be.an("array");
expect(result).to.have.length(5);
expect(result[0]).to.be.an("object");
let keys: string[] = [];
keys = keys.concat(tests.expensive.metrics.map((item) => item.name));
keys = keys.concat(tests.expensive.dimensions.map((item) => item.name));
result.forEach((row: any) => {
expect(row).to.be.an("object");
expect(row).to.have.all.keys(keys);
});
})
.end(done);
});
});
......@@ -42,7 +42,7 @@ describe("API engine controller", () => {
.expect((res: any) => {
let result = res.body;
expect(result).to.be.an("array");
expect(result).to.have.length(7);
expect(result).to.have.length(8);
})
.end(done);
});
......@@ -53,7 +53,7 @@ describe("API engine controller", () => {
.expect((res: any) => {
let result = res.body;
expect(result).to.be.an("array");
expect(result).to.have.length(16);
expect(result).to.have.length(19);
})
.end(done);
});
......
......@@ -53,4 +53,14 @@ describe("API error middleware", () => {
})
.end(done);
});
it("should respond 400 when you connect, and do not send data", (done) => {
request(server)
.post("/v1/collect/Seller")
.expect(400)
.expect((res: any) => {
expect(res.body).to.be.an("object");
expect(res.body).to.have.property("error");
})
.end(done);
});
});
......@@ -36,11 +36,6 @@ export abstract class Adapter {
* @param cb.result - Data got from view.
*/
public abstract getDataFromView(view: View, cb: (err: Error, result: any[]) => void): void;
/**
* Materialize a given view.
* @param view - View to be materialized.
*/
public abstract materializeView(view: View): boolean;
/**
* Asynchronously insert one register into a given Source.
* @param source - Insertion "location".
......
......@@ -23,6 +23,8 @@ import { Engine } from "./engine";
import { FilterOperator } from "./filter";
import { View } from "./view";
import { engineScenario } from "../../test/scenario";
import { EnumType } from "./enumType";
import { Dimension } from "./dimension";
describe("engine class", () => {
......@@ -44,7 +46,7 @@ describe("engine class", () => {
expect(optimalView.metrics).to.be.an("array");
expect(optimalView.dimensions).to.be.an("array");
expect(optimalView.metrics).to.have.length(16);
expect(optimalView.dimensions).to.have.length(16);
expect(optimalView.dimensions).to.have.length(19);
});
it("should throw an exception, query with non-existent metric", () => {
let error: boolean = false;
......@@ -288,4 +290,42 @@ describe("engine class", () => {
expect(clause.filters[0].operator).to.be.equal(operators[op]);
}
});
it("should return all views", () => {
let views: View[];
views = engine.getViews();
expect(views).to.have.length(9);
for (let i = 0 ; i < views.length; i++){
expect(views[i]).to.have.property("metrics");
expect(views[i]).to.have.property("dimensions");
expect(views[i]).to.have.property("clauses");
expect(views[i]).to.have.property("origin");
expect(views[i]).to.have.property("operation");
expect(views[i]).to.have.property("id");
expect(views[i]).to.have.property("name");
}
});
it("should return null from addView", () => {
let view: View;
view = engine.addView(null);
expect(view).to.be.null;
});
it("should return null from addDimension", () => {
let dimension: Dimension;
dimension = engine.addDimension(null);
expect(dimension).to.be.null;
});
it("should return all views", () => {
let enumtype: EnumType;
let error: boolean = false;
try {
enumtype = engine.getEnumTypeByName("test");
}
catch (e){
error = true;
expect(e.message).to.be.equal("The dataType named 'test' was not found");
}
expect(error);
});
});
......@@ -183,11 +183,12 @@ export class Filter {
return false;
}
if (op.operator === FilterOperator.GREATER ||
op.operator === FilterOperator.LOWER ||
if (op.operator === FilterOperator.GREATER ||
op.operator === FilterOperator.LOWER ||
op.operator === FilterOperator.GREATEREQ ||
op.operator === FilterOperator.LOWEREQ) {
if (op.target.dataType === DataType.DATE || op.target.dataType === DataType.INTEGER) {
op.operator === FilterOperator.LOWEREQ) {
if (op.target.dataType === DataType.DATE ||
op.target.dataType === DataType.INTEGER) {
return true;
}
......
......@@ -47,12 +47,12 @@ import { PostgresMw, MonetMw } from "./api/middlewares/adapter";
import { ErrorMw } from "./api/middlewares/error";
app.use(EngineMw(config));
if (config.adapter === "postgres") {
app.use(PostgresMw(config.connection));
if (config.adapters[0] === "postgres") {
app.use(PostgresMw(config.connections[0]));
}
else if (config.adapter === "monet") {
app.use(MonetMw(config.connection));
else if (config.adapters[0] === "monet") {
app.use(MonetMw(config.connections[0]));
}
else {
......
......@@ -91,10 +91,10 @@ interface BuildView {
}
export interface ParsedConfig {
/** What adapter is in use. */
adapter: string;
/** Connection parameters to connect in the database. */
connection: Connection;
/** What adapters are in use. */
adapters: string[];
/** Connections parameters to connect in the database. */
connections: Connection[];
/** Set of all views available. */
views: View[];
/** Set of all sources available. */
......@@ -108,6 +108,8 @@ export interface ParsedConfig {
loadViews: LoadView[];
/** SQL views build configuration. */
buildViews: BuildView[];
/** Quantity of database */
ndb: Number;
}
/** Required information to connect in a generic database. */
......@@ -169,28 +171,37 @@ export class ConfigParser {
encoding: "utf-8"
})) as ConfigSchema;
let connection: Connection = {
user: process.env.BLENDB_DB_USER,
database: process.env.BLENDB_DB_NAME,
password: process.env.BLENDB_DB_PASSWORD,
host: process.env.BLENDB_DB_HOST,
port: parseInt(process.env.BLENDB_DB_PORT, 10)
};
let connections: Connection[] = [];
let ndb: number = parseInt(process.env.BLENDB_N_DB, 10) ? parseInt(process.env.BLENDB_N_DB, 10) : 1;
let adapters: string[] = [];
for ( let i = 0; i < ndb ; i++ ){
adapters[i] = process.env["BLENDB_DB" + i + "_ADAPTER"];
connections[i] = {
user: process.env["BLENDB_DB" + i + "_USER"],
database: process.env["BLENDB_DB" + i + "_NAME"],
password: process.env["BLENDB_DB" + i + "_PASSWORD"],
host: process.env["BLENDB_DB" + i + "_HOST"],
port: parseInt(process.env["BLENDB_DB" + i + "_PORT"], 10)
};
}
let metricsOpts = config.metrics.obj;
let viewsOpts = config.views.obj;
let dimensionsOpts = config.dimensions.obj;
let enumTypesOpts = config.enumTypes.obj;
let sourcesOpts = config.sources.obj;
let parsed: ParsedConfig = {
adapter: process.env.BLENDB_ADAPTER || "postgres",
connection: connection,
adapters: adapters,
connections: connections,
views: [],
metrics: [],
enumTypes: [],
dimensions: [],
loadViews: [],
buildViews: [],
sources: []
sources: [],
ndb: ndb
};
for (let i = 0; i < config.sources.links.length; i++) {
......
......@@ -136,6 +136,10 @@ export interface AdapterScenario {
* View that contains the seller id and how many sell's he has done
*/
JoinWithAncestors: View;
/**
* View with false origin
*/
viewSumCount: View;
/**
* ParsedConfig that is load in scenario
*/
......@@ -161,4 +165,12 @@ export interface DataCtrlScenario {
* and it's id
*/
clausal: Query;
/**
* A query that returns the seller's that are registered
*/
seller: Query;
/**
* The products that are the most expensive
*/
expensive: Query;
}
[
{"met:seller:count:age" : 4, "dim:seller:status" : "active"},
{"met:seller:count:age" : 1, "dim:seller:status" : "sick leave"}
]