Skip to content

Commit

Permalink
more tests and better error messages (#1035)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianmroz-allegro authored Mar 6, 2023
1 parent 0956374 commit 9bdff2e
Showing 12 changed files with 288 additions and 75 deletions.
8 changes: 4 additions & 4 deletions src/server/routes/mkurl/mkurl.mocha.ts
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ describe("mkurl router", () => {
.send({})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "must have a dataCube" })
.expect({ error: "Parameter dataCube is required" })
.end(testComplete);
});

@@ -55,7 +55,7 @@ describe("mkurl router", () => {
.send({ dataCube: "wiki" })
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "viewDefinition must be an object" })
.expect({ error: "Parameter viewDefinition is required" })
.end(testComplete);
});

@@ -66,7 +66,7 @@ describe("mkurl router", () => {
.send({ dataCube: "wiki", viewDefinition: {} })
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "must have a viewDefinitionVersion" })
.expect({ error: "Parameter viewDefinitionVersion is required" })
.end(testComplete);
});

@@ -77,7 +77,7 @@ describe("mkurl router", () => {
.send({ dataCube: "wiki", viewDefinition: {}, viewDefinitionVersion: "foobar" })
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "unsupported viewDefinitionVersion value" })
.expect({ error: "Unsupported viewDefinitionVersion value: foobar" })
.end(testComplete);
});

2 changes: 1 addition & 1 deletion src/server/routes/plywood/plywood.mocha.ts
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ describe("plywood router", () => {
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "must have a dataCube" }, testComplete);
.expect({ error: "Parameter dataCube is required" }, testComplete);
});

it("does a query (value)", (testComplete: any) => {
258 changes: 248 additions & 10 deletions src/server/routes/query/query.mocha.ts
Original file line number Diff line number Diff line change
@@ -15,65 +15,303 @@
*/

import bodyParser from "body-parser";
import express from "express";
import { expect } from "chai";
import express, { Response } from "express";
import supertest from "supertest";
import { NOOP_LOGGER } from "../../../common/logger/logger";
import { getLogger } from "../../../common/logger/logger";
import { appSettings } from "../../../common/models/app-settings/app-settings.fixtures";
import { FilterClause } from "../../../common/models/filter-clause/filter-clause";
import { stringContains } from "../../../common/models/filter-clause/filter-clause.fixtures";
import { wikiSourcesWithExecutor } from "../../../common/models/sources/sources.fixtures";
import { Split } from "../../../common/models/split/split";
import { stringSplitCombine } from "../../../common/models/split/split.fixtures";
import { TimekeeperFixtures } from "../../../common/models/timekeeper/timekeeper.fixtures";
import { Fn, isNil } from "../../../common/utils/general/general";
import { filterDefinitionConverter } from "../../../common/view-definitions/version-4/filter-definition";
import { splitConverter } from "../../../common/view-definitions/version-4/split-definition";
import { total } from "../../../common/view-definitions/version-4/view-definition-4.fixture";
import { queryRouter } from "./query";
import { queryRouter, QueryRouterRequest } from "./query";

const settingsManagerFixture = {
getSources: () => Promise.resolve(wikiSourcesWithExecutor),
anchorPath: ".",
logger: NOOP_LOGGER,
logger: getLogger("error"),
getTimekeeper: () => TimekeeperFixtures.wiki(),
appSettings
};

const serializeSplit = (split: Split) => splitConverter.fromSplitCombine(split);
const serializeClause = (clause: FilterClause) => filterDefinitionConverter.fromFilterClause(clause);

const app = express();

app.use(bodyParser.json());

app.use("/", queryRouter(settingsManagerFixture));

describe("query router", () => {
describe("visualization endpoint", () => {
describe("QueryRouterRequest", () => {
it("should require dataCube", (testComplete: any) => {
supertest(app)
.post("/visualization")
.post("/")
.set("Content-Type", "application/json")
.send({})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "must have a dataCube" })
.expect({ error: "Parameter dataCube is required" })
.end(testComplete);
});

it("should validate viewDefinition", (testComplete: any) => {
supertest(app)
.post("/visualization")
.post("/")
.set("Content-Type", "application/json")
.send({ dataCube: "wiki" })
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "viewDefinition must be an object" })
.expect({ error: "Parameter viewDefinition is required" })
.end(testComplete);
});

it("should create context", (testComplete: Fn) => {
const withMiddleware = express()
.use(bodyParser.json())
.use("/", queryRouter(settingsManagerFixture))
.use((req: QueryRouterRequest, res: Response) => {
const { essence, dataCube, timekeeper, decorator } = req.context;
res.status(200).send({
essence: !isNil(essence),
dataCube: !isNil(dataCube),
decorator: !isNil(decorator),
timekeeper: !isNil(timekeeper)
});
});

supertest(withMiddleware)
.post("/")
.set("Content-Type", "application/json")
.send({ dataCube: "wiki", viewDefinition: total })
.expect("Content-Type", "application/json; charset=utf-8")
.expect(200)
.expect((res: supertest.Response) => {
expect(res.body).to.have.property("decorator");
expect(res.body).to.have.property("essence");
expect(res.body).to.have.property("dataCube");
expect(res.body).to.have.property("timekeeper");
})
.end(testComplete);
});
});

describe("visualization endpoint", () => {
it("should return 200 for valid parameters", (testComplete: any) => {
supertest(app)
.post("/visualization")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinitionVersion: "4",
viewDefinition: total
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(200)
.end(testComplete);
});
});

describe("raw data endpoint", () => {
it("should return 200 for valid parameters", (testComplete: any) => {
supertest(app)
.post("/raw-data")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(200)
.end(testComplete);
});
});

describe("pinboard endpoint", () => {
it("should validate split", (testComplete: any) => {
supertest(app)
.post("/pinboard")
.set("Content-Type", "application/json")
.send({ dataCube: "wiki", viewDefinition: total })
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "Parameter split is required" })
.end(testComplete);
});

it("should fail with faulty split definition", (testComplete: any) => {
supertest(app)
.post("/pinboard")
.set("Content-Type", "application/json")
// NOTE: weird TS error without `any`, like "split" was some kind of keyword
.send({
dataCube: "wiki",
viewDefinition: total,
split: true
} as any)
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "Dimension undefined not found in data cube wiki." })
.end(testComplete);
});

it("should return 200 for valid parameters", (testComplete: any) => {
const split = serializeSplit(stringSplitCombine("channel", { sort: { reference: "count" } }));

supertest(app)
.post("/pinboard")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total,
split
} as any)
.expect("Content-Type", "application/json; charset=utf-8")
.expect(200)
.end(testComplete);
});
});

describe("boolean filter endpoint", () => {
it("should validate dimension", (testComplete: any) => {
supertest(app)
.post("/boolean-filter")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "Parameter dimension is required" })
.end(testComplete);
});

it("should validate non-existent dimension", (testComplete: any) => {
supertest(app)
.post("/boolean-filter")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total,
dimension: "foobar"
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "Unknown dimension: foobar" })
.end(testComplete);
});

it("should return 200 for valid parameters", (testComplete: any) => {
supertest(app)
.post("/boolean-filter")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total,
dimension: "isRobot"
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(200)
.end(testComplete);
});
});

describe("number-filter endpoint", () => {
it("should validate dimension", (testComplete: any) => {
supertest(app)
.post("/number-filter")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "Parameter dimension is required" })
.end(testComplete);
});

it("should validate non-existent dimension", (testComplete: any) => {
supertest(app)
.post("/number-filter")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total,
dimension: "foobar"
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "Unknown dimension: foobar" })
.end(testComplete);
});

it("should return 200 for valid parameters", (testComplete: any) => {
supertest(app)
.post("/number-filter")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total,
dimension: "commentLength"
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(200)
.end(testComplete);
});
});

describe("string-filter endpoint", () => {

it("should validate clause", (testComplete: any) => {
supertest(app)
.post("/string-filter")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "Parameter clause is required" })
.end(testComplete);
});

it("should validate incorrect clause", (testComplete: any) => {
supertest(app)
.post("/string-filter")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total,
clause: "foobar"
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(400)
.expect({ error: "Dimension name cannot be empty." })
.end(testComplete);
});

it("should return 200 for valid parameters", (testComplete: any) => {
const clause = serializeClause(stringContains("channel", "e"));
supertest(app)
.post("/string-filter")
.set("Content-Type", "application/json")
.send({
dataCube: "wiki",
viewDefinition: total,
clause
})
.expect("Content-Type", "application/json; charset=utf-8")
.expect(200)
.end(testComplete);
});
});
});
12 changes: 8 additions & 4 deletions src/server/utils/request-params/parse-data-cube.ts
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
import { Request } from "express";
import { isQueryable, QueryableDataCube } from "../../../common/models/data-cube/queryable-data-cube";
import { getDataCube, Sources } from "../../../common/models/sources/sources";
import { isNil } from "../../../common/utils/general/general";
import { checkAccess } from "../datacube-guard/datacube-guard";
import { AccessDeniedError, InvalidRequestError } from "../request-errors/request-errors";
import { SettingsManager } from "../settings-manager/settings-manager";
@@ -32,8 +33,11 @@ function verifyAccess(req: Request, dataCube: QueryableDataCube): boolean {

export async function parseDataCube(req: Request, settings: Pick<SettingsManager, "getSources">): Promise<QueryableDataCube> {
const dataCubeName = req.body.dataCube || req.body.dataCubeName || req.body.dataSource; // back compatibility
if (isNil(dataCubeName)) {
throw new InvalidRequestError("Parameter dataCube is required");
}
if (typeof dataCubeName !== "string") {
throw new InvalidRequestError("must have a dataCube");
throw new InvalidRequestError(`Expected dataCube to be a string, got: ${typeof dataCubeName}`);
}
let sources: Sources;
try {
@@ -43,15 +47,15 @@ export async function parseDataCube(req: Request, settings: Pick<SettingsManager
}
const dataCube = getDataCube(sources, dataCubeName);
if (!dataCube) {
throw new InvalidRequestError("unknown data cube");
throw new InvalidRequestError(`Unknown Data Cube: ${dataCube.name}`);
}

if (!isQueryable(dataCube)) {
throw new InvalidRequestError("un queryable data cube");
throw new InvalidRequestError(`Data Cube ${dataCube.name} is not queryable`);
}

if (!verifyAccess(req, dataCube)) {
throw new AccessDeniedError("access denied");
throw new AccessDeniedError("Access denied");
}

return dataCube;
Loading

0 comments on commit 9bdff2e

Please sign in to comment.