Skip to content

Commit

Permalink
Api unit tests (#15048)
Browse files Browse the repository at this point in the history
  • Loading branch information
benmartin-coforma authored Feb 12, 2025
1 parent ddecf84 commit 638bceb
Show file tree
Hide file tree
Showing 38 changed files with 4,903 additions and 7 deletions.
114 changes: 114 additions & 0 deletions services/app-api/auth/authConditions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, expect, test, vi } from "vitest";
import {
authorizeAdmin,
authorizeAnyUser,
authorizeAdminOrUserWithEmail,
authorizeAdminOrUserForState,
authorizeUserForState,
authorizeStateUser,
authorizeUserForState
} from "./authConditions.js";
import { getCurrentUserInfo } from "./cognito-auth.js";

vi.mock("./cognito-auth.js", () => ({
getCurrentUserInfo: vi.fn(),
}));

const mockEvent = {};

const stateUserCO = {
email: "[email protected]",
role: "state",
states: ["CO"],
};

const stateUserTX = {
email: "[email protected]",
role: "state",
states: ["TX"],
};

const adminUser = {
email: "[email protected]",
role: "admin",
states: [],
};

const businessUser = {
email: "[email protected]",
role: "business",
states: ["CO", "TX", "etc"],
};

const assertAllow = async (authCall, user) => {
getCurrentUserInfo.mockResolvedValueOnce({ data: user });
await expect(authCall()).resolves.not.toThrow();
};

const assertDeny = async (authCall, user) => {
getCurrentUserInfo.mockResolvedValueOnce({ data: user });
await expect(authCall()).rejects.toThrow("Forbidden");
};

describe("authConditions", () => {
test("authorizeAnyUser should allow the expected users", async () => {
const authCall = () => authorizeAnyUser(mockEvent);
await assertAllow(authCall, adminUser);
await assertAllow(authCall, businessUser);
await assertAllow(authCall, stateUserCO);
await assertAllow(authCall, stateUserTX);
});

test("authorizeAnyUser should reject when a user cannot be found", async () => {
getCurrentUserInfo.mockResolvedValueOnce(undefined);
await expect(authorizeAnyUser(mockEvent)).rejects.toThrow();

getCurrentUserInfo.mockResolvedValueOnce({ data: {} });
await expect(authorizeAnyUser(mockEvent)).rejects.toThrow();
});

test("authorizeAnyUser should reject token decoding fails", async () => {
getCurrentUserInfo.mockImplementationOnce(() => { throw new Error(); });
await expect(authorizeAnyUser(mockEvent)).rejects.toThrow();
});

test("authorizeAdmin should allow the expected users", async () => {
const authCall = () => authorizeAdmin(mockEvent);
await assertAllow(authCall, adminUser);
await assertDeny(authCall, businessUser);
await assertDeny(authCall, stateUserCO);
await assertDeny(authCall, stateUserTX);
});

test("authorizeAdminOrUserWithEmail should allow the expected users", async () => {
const authCall = () => authorizeAdminOrUserWithEmail(mockEvent, "[email protected]");
await assertAllow(authCall, adminUser);
await assertDeny(authCall, businessUser);
await assertAllow(authCall, stateUserCO);
await assertDeny(authCall, stateUserTX);
});

test("authorizeAdminOrUserForState should allow the expected users", async () => {
const authCall = () => authorizeAdminOrUserForState(mockEvent, "CO");
await assertAllow(authCall, adminUser);
await assertAllow(authCall, businessUser);
await assertAllow(authCall, stateUserCO);
await assertDeny(authCall, stateUserTX);
});

test("authorizeStateUser should allow the expected users", async () => {
const authCall = () => authorizeStateUser(mockEvent, "CO");
await assertDeny(authCall, adminUser);
await assertDeny(authCall, businessUser);
await assertAllow(authCall, stateUserCO);
await assertDeny(authCall, stateUserTX);
});

test("authorizeUserForState should allow the expected users", async () => {
const authCall = () => authorizeUserForState(mockEvent, "CO");
await assertDeny(authCall, adminUser);
await assertAllow(authCall, businessUser);
await assertAllow(authCall, stateUserCO);
await assertDeny(authCall, stateUserTX);
});
});
54 changes: 54 additions & 0 deletions services/app-api/auth/cognito-auth.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, it, vi } from "vitest";
import { getCurrentUserInfo } from "./cognito-auth.js";
import { getUserDetailsFromEvent } from "../libs/authorization.js";
import { obtainUserByEmail } from "../handlers/users/post/obtainUserByEmail.js";

vi.mock("../libs/authorization.js", () => ({
getUserDetailsFromEvent: vi.fn(),
}));

vi.mock("../handlers/users/post/obtainUserByEmail.js", () => ({
obtainUserByEmail: vi.fn(),
}));

const mockEvent = {};
const mockUser = {
email: "[email protected]",
role: "state",
states: ["CO"],
};

describe("getCurrentUserInfo", () => {
it("should return the user if they can be found", async () => {
getUserDetailsFromEvent.mockResolvedValueOnce({ email: mockUser.email });
obtainUserByEmail.mockResolvedValueOnce({ Items: [mockUser] });

const response = await getCurrentUserInfo(mockEvent);

expect(response).toEqual({
status: "success",
data: mockUser,
});
expect(obtainUserByEmail).toHaveBeenCalledWith(mockUser.email);
});

it("should return a shell user if none can be found", async () => {
getUserDetailsFromEvent.mockResolvedValueOnce({ email: mockUser.email });
obtainUserByEmail.mockResolvedValueOnce(undefined);

const response = await getCurrentUserInfo(mockEvent);

expect(response).toEqual({
data: {
email: mockUser.email,
}
});
expect(obtainUserByEmail).toHaveBeenCalledWith(mockUser.email);
});

it("should throw if token cannot be decoded", async () => {
getUserDetailsFromEvent.mockRejectedValueOnce("nope");

await expect(getCurrentUserInfo(mockEvent)).rejects.toThrow();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { main as obtainFormTemplate } from "./obtainFormTemplate.js";
import { authorizeAdmin } from "../../../auth/authConditions.js";
import {
DynamoDBDocumentClient,
QueryCommand,
} from "@aws-sdk/lib-dynamodb";
import { mockClient } from "aws-sdk-client-mock";

vi.mock("../../../auth/authConditions.js", () => ({
authorizeAdmin: vi.fn(),
}));

const mockDynamo = mockClient(DynamoDBDocumentClient);

const mockEvent = {
body: JSON.stringify({ year: 2025 }),
};

const mockFormTemplate = {
mockProp: "mockValue",
};

describe("obtainFormTemplate.js", () => {
beforeEach(() => {
mockDynamo.reset();
});

it("should query dynamo for the appropriate data", async () => {
const mockQuery = vi.fn().mockResolvedValueOnce({
Count: 1,
Items: [mockFormTemplate],
});
mockDynamo.on(QueryCommand).callsFakeOnce(mockQuery);

const response = await obtainFormTemplate(mockEvent);

expect(response).toEqual(expect.objectContaining({
statusCode: 200,
body: JSON.stringify([mockFormTemplate]),
}));
expect(mockQuery).toHaveBeenCalledWith({
TableName: "local-form-templates",
ExpressionAttributeNames: { "#theYear": "year" },
ExpressionAttributeValues: { ":year": 2025 },
KeyConditionExpression: "#theYear = :year",
}, expect.any(Function));
});

it("should return Not Found if there are no results", async () => {
const mockQuery = vi.fn().mockResolvedValueOnce({ Count: 0 });
mockDynamo.on(QueryCommand).callsFakeOnce(mockQuery);

const response = await obtainFormTemplate(mockEvent);

expect(response).toEqual(expect.objectContaining({
statusCode: 200,
body: JSON.stringify({
status: 404,
message: "Could not find form template for year: 2025",
}),
}));
});

it("should return Internal Server Error if the user is not an admin", async () => {
authorizeAdmin.mockRejectedValueOnce(new Error("Forbidden"));

const response = await obtainFormTemplate(mockEvent);

expect(response).toEqual(expect.objectContaining({
statusCode: 500,
body: JSON.stringify({ error: "Forbidden" }),
}));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { main as obtainFormTemplateYears } from "./obtainFormTemplateYears.js";
import { authorizeAdmin } from "../../../auth/authConditions.js";
import {
DynamoDBDocumentClient,
ScanCommand,
} from "@aws-sdk/lib-dynamodb";
import { mockClient } from "aws-sdk-client-mock";

vi.mock("../../../auth/authConditions.js", () => ({
authorizeAdmin: vi.fn(),
}));

const mockDynamo = mockClient(DynamoDBDocumentClient);

const mockEvent = {};

describe("obtainFormTemplateYears.js", () => {
beforeEach(() => {
mockDynamo.reset();
});

it("should query dynamo for the appropriate data", async () => {
const mockScan = vi.fn().mockResolvedValueOnce({
Count: 1,
Items: [{ year: 2024 }, { year: 2023 }, { year: 2025 }],
});
mockDynamo.on(ScanCommand).callsFakeOnce(mockScan);

const response = await obtainFormTemplateYears(mockEvent);

expect(response).toEqual(expect.objectContaining({
statusCode: 200,
// Note the sort order: most recent year first
body: JSON.stringify([2025, 2024, 2023]),
}));
expect(mockScan).toHaveBeenCalledWith({
TableName: "local-form-templates",
ExpressionAttributeNames: { "#theYear": "year" },
ProjectionExpression: "#theYear",
}, expect.any(Function));
});

it("should return Not Found if there are no results", async () => {
const mockScan = vi.fn().mockResolvedValueOnce({ Count: 0 });
mockDynamo.on(ScanCommand).callsFakeOnce(mockScan);

const response = await obtainFormTemplateYears(mockEvent);

expect(response).toEqual(expect.objectContaining({
statusCode: 200,
body: JSON.stringify([]),
}));
});

it("should return Internal Server Error if the user is not an admin", async () => {
authorizeAdmin.mockRejectedValueOnce(new Error("Forbidden"));

const response = await obtainFormTemplateYears(mockEvent);

expect(response).toEqual(expect.objectContaining({
statusCode: 500,
body: JSON.stringify({ error: "Forbidden" }),
}));
});
});
Loading

0 comments on commit 638bceb

Please sign in to comment.