Skip to content

Commit

Permalink
test: Cover error handling cases in API tests (1/2) (#2466)
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr authored Nov 29, 2023
1 parent 29889f8 commit 66e1bdc
Show file tree
Hide file tree
Showing 27 changed files with 600 additions and 31 deletions.
23 changes: 23 additions & 0 deletions api.planx.uk/client/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CoreDomainClient } from "@opensystemslab/planx-core";
import { getClient } from ".";
import { userContext } from "../modules/auth/middleware";
import { getJWT } from "../tests/mockJWT";

test("getClient() throws an error if a store is not set", () => {
expect(() => getClient()).toThrow();
});

test("getClient() returns a client if store is set", () => {
const getStoreMock = jest.spyOn(userContext, "getStore");
getStoreMock.mockReturnValue({
user: {
sub: "123",
jwt: getJWT({ role: "teamEditor" }),
},
});

const client = getClient();

expect(client).toBeDefined();
expect(client).toBeInstanceOf(CoreDomainClient);
});
24 changes: 23 additions & 1 deletion api.planx.uk/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ComponentType } from "@opensystemslab/planx-core/types";
import { dataMerged, getFormattedEnvironment, isLiveEnv } from "./helpers";
import {
dataMerged,
getFlowData,
getFormattedEnvironment,
isLiveEnv,
} from "./helpers";
import { queryMock } from "./tests/graphqlQueryMock";
import { userContext } from "./modules/auth/middleware";
import { getJWT } from "./tests/mockJWT";
Expand Down Expand Up @@ -167,6 +172,7 @@ describe("dataMerged() function", () => {
},
});
});

it("handles multiple external portal nodes", async () => {
const result = await dataMerged("parent-id");
const nodeTypes = Object.values(result).map((node) =>
Expand All @@ -180,3 +186,19 @@ describe("dataMerged() function", () => {
expect(areAllPortalsFlattened).toBe(true);
});
});

describe("getFlowData() function", () => {
it("throws an error if a flow is not found", async () => {
queryMock.mockQuery({
name: "GetFlowData",
variables: {
id: "child-id",
},
data: {
flow: null,
},
});

await expect(getFlowData("child-id")).rejects.toThrow();
});
});
12 changes: 10 additions & 2 deletions api.planx.uk/lib/hasura/metadata/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { createScheduledEvent, RequiredScheduledEventArgs } from ".";
import Axios from "axios";
import Axios, { AxiosError } from "axios";

jest.mock("axios");
jest.mock("axios", () => ({
...jest.requireActual("axios"),
post: jest.fn(),
}));
const mockAxios = Axios as jest.Mocked<typeof Axios>;

const mockScheduledEvent: RequiredScheduledEventArgs = {
Expand All @@ -16,6 +19,11 @@ test("createScheduledEvent returns an error if request fails", async () => {
await expect(createScheduledEvent(mockScheduledEvent)).rejects.toThrow();
});

test("createScheduledEvent returns an error if Axios errors", async () => {
mockAxios.post.mockRejectedValue(new AxiosError());
await expect(createScheduledEvent(mockScheduledEvent)).rejects.toThrow();
});

test("createScheduledEvent returns response data on success", async () => {
mockAxios.post.mockResolvedValue({ data: "test data" });
await expect(createScheduledEvent(mockScheduledEvent)).resolves.toBe(
Expand Down
12 changes: 10 additions & 2 deletions api.planx.uk/lib/hasura/schema/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { runSQL } from ".";
import Axios from "axios";
import Axios, { AxiosError } from "axios";

jest.mock("axios");
jest.mock("axios", () => ({
...jest.requireActual("axios"),
post: jest.fn(),
}));
const mockAxios = Axios as jest.Mocked<typeof Axios>;

const sql = "SELECT * FROM TEST";
Expand All @@ -11,6 +14,11 @@ test("runSQL returns an error if request fails", async () => {
await expect(runSQL(sql)).rejects.toThrow();
});

test("runSQL returns an error if Axios errors", async () => {
mockAxios.post.mockRejectedValue(new AxiosError());
await expect(runSQL(sql)).rejects.toThrow();
});

test("runSQL returns response data on success", async () => {
mockAxios.post.mockResolvedValue({ data: "test data" });
await expect(runSQL(sql)).resolves.toBe("test data");
Expand Down
39 changes: 39 additions & 0 deletions api.planx.uk/lib/notify/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { sendEmail } from ".";
import { NotifyClient } from "notifications-node-client";
import { NotifyConfig } from "../../types";

jest.mock("notifications-node-client");

const TEST_EMAIL = "[email protected]";
const mockConfig: NotifyConfig = {
personalisation: {
teamName: "test",
emailReplyToId: "test",
helpEmail: "test",
helpOpeningHours: "test",
helpPhone: "test",
},
};

describe("sendEmail", () => {
it("throws an error if an invalid template is used", async () => {
await expect(
// @ts-expect-error Argument of type "invalidTemplate" is not assignable to parameter
sendEmail("invalidTemplate", "[email protected]", {}),
).rejects.toThrow();
});

it("throw an error if an error is thrown within sendEmail()", async () => {
const mockNotifyClient = NotifyClient.mock.instances[0];
mockNotifyClient.sendEmail.mockRejectedValue(new Error());
await expect(sendEmail("save", TEST_EMAIL, mockConfig)).rejects.toThrow();
});

it("throw an error if the NotifyClient errors", async () => {
const mockNotifyClient = NotifyClient.mock.instances[0];
mockNotifyClient.sendEmail.mockRejectedValue({
response: { data: { errors: ["Invalid email"] } },
});
await expect(sendEmail("save", TEST_EMAIL, mockConfig)).rejects.toThrow();
});
});
2 changes: 1 addition & 1 deletion api.planx.uk/lib/notify/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const sendEmail = async (
expiryDate?: string;
} = { message: "Success" };
if (template === "expiry")
softDeleteSession(config.personalisation.sessionId!);
await softDeleteSession(config.personalisation.sessionId!);
if (template === "save")
returnValue.expiryDate = config.personalisation.expiryDate;
return returnValue;
Expand Down
12 changes: 12 additions & 0 deletions api.planx.uk/modules/admin/session/bops.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ describe("BOPS payload admin endpoint", () => {
.expect(403);
});

it("returns an error if the BOPS payload generation fails", async () => {
mockGenerateBOPSPayload.mockRejectedValueOnce("Error!");

await supertest(app)
.get(endpoint`123`)
.set(authHeader({ role: "platformAdmin" }))
.expect(500)
.then((res) => {
expect(res.body.error).toMatch(/Failed to get BOPS payload/);
});
});

it("returns a JSON payload", async () => {
await supertest(app)
.get(endpoint`123`)
Expand Down
16 changes: 16 additions & 0 deletions api.planx.uk/modules/admin/session/digitalPlanningData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ describe("Digital Planning Application payload admin endpoint", () => {
.expect(403);
});

it("returns an error if the Digital Planning payload generation fails", async () => {
mockGenerateDigitalPlanningApplicationPayload.mockRejectedValueOnce(
"Error!",
);

await supertest(app)
.get(endpoint`123`)
.set(authHeader({ role: "platformAdmin" }))
.expect(500)
.then((res) => {
expect(res.body.error).toMatch(
/Failed to make Digital Planning Application payload/,
);
});
});

it("returns a valid JSON payload", async () => {
await supertest(app)
.get(endpoint`123`)
Expand Down
12 changes: 12 additions & 0 deletions api.planx.uk/modules/admin/session/oneAppXML.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ describe("OneApp XML endpoint", () => {
.expect(403);
});

it("returns an error if the XML generation fails", async () => {
mockGenerateOneAppXML.mockRejectedValueOnce("Error!");

await supertest(app)
.get(endpoint`123`)
.set(authHeader({ role: "platformAdmin" }))
.expect(500)
.then((res) => {
expect(res.body.error).toMatch(/Failed to get OneApp XML/);
});
});

it("returns XML", async () => {
await supertest(app)
.get(endpoint`123`)
Expand Down
4 changes: 4 additions & 0 deletions api.planx.uk/modules/admin/session/summary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ describe("Session summary admin endpoint", () => {
.expect(403);
});

it.todo("returns an error if the service fails");

it.todo("returns an error if the session can't be found");

it("returns JSON", async () => {
await supertest(app)
.get(endpoint`abc123`)
Expand Down
2 changes: 2 additions & 0 deletions api.planx.uk/modules/admin/session/zip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ describe("zip data admin endpoint", () => {
.expect(200)
.expect("content-type", "application/zip");
});

it.todo("returns an error if the service fails");
});
51 changes: 51 additions & 0 deletions api.planx.uk/modules/flows/copyFlow/copyFlow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { queryMock } from "../../../tests/graphqlQueryMock";
import { authHeader } from "../../../tests/mockJWT";
import app from "../../../server";
import { Flow } from "../../../types";
import { userContext } from "../../auth/middleware";

beforeEach(() => {
queryMock.mockQuery({
Expand Down Expand Up @@ -174,6 +175,56 @@ it("inserts copied unique flow data", async () => {
});
});

it("throws an error if the a GraphQL operation fails", async () => {
const body = {
insert: true,
replaceValue: "T3ST1",
};

queryMock.mockQuery({
name: "GetFlowData",
matchOnVariables: false,
data: {
flow: {
data: null,
},
},
graphqlErrors: [
{
message: "Something went wrong",
},
],
});

await supertest(app)
.post("/flows/1/copy")
.send(body)
.set(auth)
.expect(500)
.then((res) => {
expect(res.body.error).toMatch(/Failed to copy flow/);
});
});

it("throws an error if user details are missing", async () => {
const getStoreMock = jest.spyOn(userContext, "getStore");
getStoreMock.mockReturnValue(undefined);

const body = {
insert: true,
replaceValue: "T3ST1",
};

await supertest(app)
.post("/flows/1/copy")
.send(body)
.set(auth)
.expect(500)
.then((res) => {
expect(res.body.error).toMatch(/Failed to copy flow/);
});
});

// the original flow
const mockFlowData: Flow["data"] = {
_root: {
Expand Down
1 change: 0 additions & 1 deletion api.planx.uk/modules/flows/copyFlowAsPortal/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Flow } from "../../../types";
const copyPortalAsFlow = async (flowId: string, portalNodeId: string) => {
// fetch the parent flow data
const flow = await getFlowData(flowId);
if (!flow) throw Error("Unknown flowId");

// confirm that the node id provided is a valid portal
if (
Expand Down
25 changes: 25 additions & 0 deletions api.planx.uk/modules/flows/findReplace/findReplace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,31 @@ describe("string replacement", () => {
});
});
});

it("returns an error thrown by the service", async () => {
queryMock.mockQuery({
name: "GetFlowData",
matchOnVariables: false,
data: {
flow: {
data: null,
},
},
graphqlErrors: [
{
message: "Something went wrong",
},
],
});

await supertest(app)
.post("/flows/1/search?find=designated.monument&replace=monument")
.set(auth)
.expect(500)
.then((res) => {
expect(res.body.error).toMatch(/Failed to find and replace/);
});
});
});

describe("HTML replacement", () => {
Expand Down
1 change: 0 additions & 1 deletion api.planx.uk/modules/flows/findReplace/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ const findAndReplaceInFlow = async (
replace?: string,
) => {
const flow = await getFlowData(flowId);
if (!flow) throw Error("Unknown flowId");

// Find
if (!replace) {
Expand Down
Loading

0 comments on commit 66e1bdc

Please sign in to comment.