Skip to content

Commit

Permalink
Merge pull request #12 from nulib/integration-tests
Browse files Browse the repository at this point in the history
Add integration tests and improve mocha config
  • Loading branch information
mbklein authored Aug 16, 2022
2 parents be52fed + 9df7f99 commit cb8de00
Show file tree
Hide file tree
Showing 28 changed files with 375 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .mocharc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
ignore: ["test/test-helpers/**/*"],
recursive: true,
require: "test/test-helpers"
}
2 changes: 1 addition & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
tests/*
test/*
2 changes: 1 addition & 1 deletion nyc.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const defaultExclude = require('@istanbuljs/schema/default-exclude');
const localExclude = ["src/handlers/**"];
const localExclude = [".aws-sam/**/*"];
module.exports = {
all: true,
"check-coverage": true,
Expand Down
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
"@aws-sdk/protocol-http": "^3.127.0",
"@aws-sdk/signature-v4": "^3.130.0",
"axios": ">=0.21.1",
"base64-js": "^1.5.1",
"lz-string": "^1.4.4"
},
"scripts": {
"prettier": "prettier -c src tests",
"prettier:fix": "prettier -cw src tests",
"test": "mocha --require tests/test-helpers --recursive tests/unit",
"prettier": "prettier -c src test",
"prettier:fix": "prettier -cw src test",
"test": "mocha",
"test:coverage": "nyc npm test"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions src/api/opensearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ async function getDocument(index, id) {
}

function isVisible(doc) {
if (!doc?.found) {
return false;
}
if (doc?._source.api_model == "FileSet") {
return doc?._source?.visibility !== "Private";
} else {
Expand Down
10 changes: 10 additions & 0 deletions src/handlers/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const base64 = require("base64-js");

function decodeEventBody(event) {
if (!event.isBase64Encoded) return event;
event.body = new Buffer.from(base64.toByteArray(event.body)).toString();
event.isBase64Encoded = false;
return event;
}

module.exports = { decodeEventBody };
2 changes: 2 additions & 0 deletions src/handlers/search.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { decodeEventBody } = require("./middleware");
const { baseUrl } = require("../helpers");
const { modelsToTargets, validModels } = require("../api/request/models");
const { search } = require("../api/opensearch");
Expand Down Expand Up @@ -25,6 +26,7 @@ const getSearch = async (event) => {
};

const postSearch = async (event) => {
event = decodeEventBody(event);
const eventBody = JSON.parse(event.body);

const requestedModels =
Expand Down
12 changes: 8 additions & 4 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
const gatewayRe = /execute-api.[a-z]+-[a-z]+-\d+.amazonaws.com/;

function getHeader(event, name) {
return event.headers[name] || event.headers[name.toLowerCase()];
}

function baseUrl(event) {
const scheme = event.headers["X-Forwarded-Proto"];
const scheme = getHeader(event, "X-Forwarded-Proto");

// The localhost check only matters in dev mode, but it's
// really inconvenient not to have it
const host =
event.requestContext.domainName === "localhost"
? event.headers["Host"].split(/:/)[0]
? getHeader(event, "Host").split(/:/)[0]
: event.requestContext.domainName;
const port = event.headers["X-Forwarded-Port"];
const port = getHeader(event, "X-Forwarded-Port");

let result = new URL(`${scheme}://${host}:${port}`);
const stage = event.requestContext?.stage;
Expand All @@ -20,4 +24,4 @@ function baseUrl(event) {
return result.toString();
}

module.exports = { baseUrl };
module.exports = { baseUrl, getHeader };
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
93 changes: 93 additions & 0 deletions test/integration/get-doc.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use strict";

const chai = require("chai");
const expect = chai.expect;

describe("Doc retrieval routes", () => {
helpers.saveEnvironment();
const mock = helpers.mockIndex();

describe("GET /works/{id}", () => {
const { handler } = require("../../src/handlers/get-work-by-id");

it("retrieves a single work", async () => {
mock
.get("/dc-v2-work/_doc/1234")
.reply(200, helpers.testFixture("mocks/work-1234.json"));

const { event } = helpers
.mockEvent("GET", "/works/{id}")
.pathParams({ id: 1234 });
const result = await handler(event);
expect(result.statusCode).to.eq(200);

const resultBody = JSON.parse(result.body);
expect(resultBody.data.api_model).to.eq("Work");
expect(resultBody.data.id).to.eq("1234");
});

it("404s a missing work", async () => {
mock
.get("/dc-v2-work/_doc/1234")
.reply(200, helpers.testFixture("mocks/missing-work-1234.json"));

const { event } = helpers
.mockEvent("GET", "/works/{id}")
.pathParams({ id: 1234 });
const result = await handler(event);
expect(result.statusCode).to.eq(404);
});

it("404s an unpublished work", async () => {
mock
.get("/dc-v2-work/_doc/1234")
.reply(200, helpers.testFixture("mocks/unpublished-work-1234.json"));

const { event } = helpers
.mockEvent("GET", "/works/{id}")
.pathParams({ id: 1234 });
const result = await handler(event);
expect(result.statusCode).to.eq(404);
});
});

describe("GET /collections/{id}", () => {
const { handler } = require("../../src/handlers/get-collection-by-id");

it("retrieves a single collection", async () => {
mock
.get("/dc-v2-collection/_doc/1234")
.reply(200, helpers.testFixture("mocks/collection-1234.json"));

const { event } = helpers
.mockEvent("GET", "/collections/{id}")
.pathParams({ id: 1234 });
const result = await handler(event);
expect(result.statusCode).to.eq(200);

const resultBody = JSON.parse(result.body);
expect(resultBody.data.api_model).to.eq("Collection");
expect(resultBody.data.id).to.eq("1234");
});
});

describe("GET /file-sets/{id}", () => {
const { handler } = require("../../src/handlers/get-file-set-by-id");

it("retrieves a single file-set", async () => {
mock
.get("/dc-v2-file-set/_doc/1234")
.reply(200, helpers.testFixture("mocks/fileset-1234.json"));

const { event } = helpers
.mockEvent("GET", "/file-sets/{id}")
.pathParams({ id: 1234 });
const result = await handler(event);
expect(result.statusCode).to.eq(200);

const resultBody = JSON.parse(result.body);
expect(resultBody.data.api_model).to.eq("FileSet");
expect(resultBody.data.id).to.eq("1234");
});
});
});
113 changes: 113 additions & 0 deletions test/integration/search.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"use strict";

const chai = require("chai");
const expect = chai.expect;
const searchHandlers = require("../../src/handlers/search");
const RequestPipeline = require("../../src/api/request/pipeline");

describe("Search routes", () => {
helpers.saveEnvironment();
const mock = helpers.mockIndex();

describe("POST /search/{targets}", () => {
const handler = searchHandlers.postSearch;
const originalQuery = { query: { match_all: {} } };
const authQuery = new RequestPipeline(originalQuery).authFilter().toJson();

it("performs a works search by default", async () => {
mock
.post("/dc-v2-work/_search", authQuery)
.reply(200, helpers.testFixture("mocks/search.json"));
const { event } = helpers
.mockEvent("POST", "/search")
.body(originalQuery);
const result = await handler(event);
expect(result.statusCode).to.eq(200);

const resultBody = JSON.parse(result.body);
expect(resultBody).to.include.keys(["data", "pagination"]);
expect(resultBody.data.length).to.eq(10);
});

it("performs a search on specified models", async () => {
mock
.post("/dc-v2-work,dc-v2-collection/_search", authQuery)
.reply(200, helpers.testFixture("mocks/search-multiple-targets.json"));
const { event } = helpers
.mockEvent("POST", "/search/{models}")
.pathParams({ models: "works,collections" })
.body(originalQuery)
.base64Encode();
const result = await handler(event);
expect(result.statusCode).to.eq(200);

const resultBody = JSON.parse(result.body);
expect(resultBody).to.include.keys(["data", "pagination"]);
expect(resultBody.data.length).to.eq(10);
});

it("errors if invalid models specified", async () => {
const { event } = helpers
.mockEvent("POST", "/search/{models}")
.pathParams({ models: "works,collections,blargh" })
.body(originalQuery);
const result = await handler(event);
expect(result.statusCode).to.eq(400);

const resultBody = JSON.parse(result.body);
expect(resultBody.message).to.eq(
"Invalid models requested: works,collections,blargh"
);
});
});

describe("GET /search", () => {
const handler = searchHandlers.getSearch;
const originalQuery = { query: { match_all: {} }, size: 10, from: 0 };
const authQuery = new RequestPipeline(originalQuery).authFilter().toJson();
const searchToken =
"N4IgRg9gJgniBcoCOBXApgJzokBbAhgC4DGAFgPr4A2VCwAvvQDQgDOAlgF5oICMADMzzQ0VVggDaIAO4QMAa3EBdekA";

it("requires a searchToken", async () => {
const { event } = helpers.mockEvent("GET", "/search");
const result = await handler(event);
expect(result.statusCode).to.eq(400);
const resultBody = JSON.parse(result.body);
expect(resultBody.message).to.eq("searchToken parameter is required");
});

it("requires a valid searchToken", async () => {
const { event } = helpers
.mockEvent("GET", "/search")
.queryParams({ searchToken: "Ceci n'est pas une searchToken" });
const result = await handler(event);
expect(result.statusCode).to.eq(400);
const resultBody = JSON.parse(result.body);
expect(resultBody.message).to.eq("searchToken is invalid");
});

it("performs a search using a searchToken and page number", async () => {
mock
.post("/dc-v2-work/_search", authQuery)
.reply(200, helpers.testFixture("mocks/search.json"));

const { event } = helpers
.mockEvent("GET", "/search")
.queryParams({ searchToken, page: 1 });
const result = await handler(event);
expect(result.statusCode).to.eq(200);
});

it("defaults to page 1", async () => {
mock
.post("/dc-v2-work/_search", authQuery)
.reply(200, helpers.testFixture("mocks/search.json"));

const { event } = helpers
.mockEvent("GET", "/search")
.queryParams({ searchToken });
const result = await handler(event);
expect(result.statusCode).to.eq(200);
});
});
});
Loading

0 comments on commit cb8de00

Please sign in to comment.