Skip to content

Commit

Permalink
Refactor amplify methods to single file (#11881)
Browse files Browse the repository at this point in the history
  • Loading branch information
bangbay-bluetiger authored Oct 28, 2024
1 parent 882bc9d commit 26e7fb4
Show file tree
Hide file tree
Showing 17 changed files with 395 additions and 279 deletions.
14 changes: 9 additions & 5 deletions services/ui-src/src/components/cards/TemplateCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { useStore } from "utils";
// verbiage
import verbiage from "verbiage/pages/home";

const mockAPI = require("utils/api/requestMethods/getTemplateUrl");
const mockGetSignedTemplateUrl = jest.fn();

jest.mock("utils/api/requestMethods/getTemplateUrl", () => ({
getSignedTemplateUrl: () => mockGetSignedTemplateUrl(),
}));

jest.mock("utils/other/useBreakpoint", () => ({
useBreakpoint: jest.fn(() => ({
Expand Down Expand Up @@ -65,13 +69,14 @@ describe("Test MCPAR TemplateCard", () => {
});

test("MCPAR TemplateCard download button is visible and clickable", async () => {
const apiSpy = jest.spyOn(mockAPI, "getSignedTemplateUrl");
const downloadButton = screen.getByText(mcparTemplateVerbiage.downloadText);
expect(downloadButton).toBeVisible();
await act(async () => {
await userEvent.click(downloadButton);
});
await waitFor(() => expect(apiSpy).toHaveBeenCalledTimes(1));
await waitFor(() =>
expect(mockGetSignedTemplateUrl).toHaveBeenCalledTimes(1)
);
});

test("MCPAR TemplateCard image is visible on desktop", () => {
Expand Down Expand Up @@ -103,13 +108,12 @@ describe("Test MLR TemplateCard", () => {
});

test("MLR TemplateCard download button is visible and clickable", async () => {
const apiSpy = jest.spyOn(mockAPI, "getSignedTemplateUrl");
const downloadButton = screen.getByText(mlrTemplateVerbiage.downloadText);
expect(downloadButton).toBeVisible();
await act(async () => {
await userEvent.click(downloadButton);
});
await waitFor(() => expect(apiSpy).toHaveBeenCalled());
await waitFor(() => expect(mockGetSignedTemplateUrl).toHaveBeenCalled());
});

test("MLR TemplateCard image is visible on desktop", () => {
Expand Down
15 changes: 7 additions & 8 deletions services/ui-src/src/components/logins/LoginCognito.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { LoginCognito } from "components";
// utils
import { RouterWrappedComponent } from "utils/testing/setupJest";

const mockSignIn = jest.fn();
jest.mock("aws-amplify/auth", () => ({
signIn: (credentials: any) => mockSignIn(credentials),
const mockLoginUser = jest.fn();

jest.mock("utils", () => ({
loginUser: (username: string, password: string) =>
mockLoginUser(username, password),
}));

const mockUseNavigate = jest.fn();
Expand All @@ -30,12 +32,9 @@ describe("Test LoginCognito", () => {
const passwordInput = screen.getByLabelText("Password");
const submitButton = screen.getByRole("button");
await userEvent.type(emailInput, "[email protected]");
await userEvent.type(passwordInput, "p@$$w0rd"); //pragma: allowlist secret
await userEvent.type(passwordInput, "test");
await userEvent.click(submitButton);
expect(mockSignIn).toHaveBeenCalledWith({
username: "[email protected]",
password: "p@$$w0rd", //pragma: allowlist secret
});
expect(mockLoginUser).toHaveBeenCalledWith("[email protected]", "test");
expect(mockUseNavigate).toHaveBeenCalledWith("/");
});
});
Expand Down
6 changes: 3 additions & 3 deletions services/ui-src/src/components/logins/LoginCognito.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { signIn } from "aws-amplify/auth";
// components
import { Box, Button, Heading, Input, Stack, Text } from "@chakra-ui/react";
import { ErrorAlert } from "components";
import { ErrorVerbiage } from "types";
import { loginUser } from "utils";

const useFormFields = (initialState: any) => {
const [fields, setValues] = useState(initialState);
Expand Down Expand Up @@ -32,8 +32,8 @@ export const LoginCognito = () => {
const handleLogin = async (event: any) => {
event.preventDefault();
try {
await signIn({ username: fields.email, password: fields.password });
navigate(`/`);
await loginUser(fields.email, fields.password);
navigate("/");
} catch (error: any) {
setError(error.message);
}
Expand Down
163 changes: 163 additions & 0 deletions services/ui-src/src/utils/api/apiLib.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import {
authenticateWithIDM,
del,
get,
getRequestHeaders,
getTokens,
loginUser,
logoutUser,
post,
put,
refreshSession,
} from "utils";

const mockResponse = (method?: string) => ({
response: { body: { json: () => ({ test: method }) } },
});
const mockDelete = jest.fn().mockImplementation(() => mockResponse());
const mockGet = jest.fn().mockImplementation(() => mockResponse("get"));
const mockPost = jest.fn().mockImplementation(() => mockResponse("post"));
const mockPut = jest.fn().mockImplementation(() => mockResponse("put"));
const mockSession = jest.fn();
const mockSignInWithRedirect = jest.fn();
const mockSignIn = jest.fn();
const mockSignOut = jest.fn();
const mockTimeout = jest.fn();

jest.mock("aws-amplify/api", () => ({
del: () => mockDelete(),
get: () => mockGet(),
post: () => mockPost(),
put: () => mockPut(),
}));

jest.mock("aws-amplify/auth", () => ({
fetchAuthSession: () => mockSession(),
signIn: () => mockSignIn(),
signOut: () => mockSignOut(),
signInWithRedirect: () => mockSignInWithRedirect(),
}));

jest.mock("utils/auth/authLifecycle", () => ({
updateTimeout: () => mockTimeout(),
}));

describe("utils/api/apiLib", () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe("getRequestHeaders()", () => {
test("Logs error to console if Auth throws error", async () => {
jest.spyOn(console, "log").mockImplementation(jest.fn());
const spy = jest.spyOn(console, "log");

mockSession.mockImplementation(() => {
throw new Error();
});

await getRequestHeaders();

expect(spy).toHaveBeenCalledTimes(1);
});

test("Returns token if current idToken exists", async () => {
mockSession.mockResolvedValue({
tokens: {
idToken: {
toString: () => "stringToken",
},
},
});

const result = await getRequestHeaders();

expect(result).toStrictEqual({ "x-api-key": "stringToken" });
});
});

test("getTokens()", async () => {
await getTokens();
expect(mockSession).toHaveBeenCalledTimes(1);
});

test("authenticateWithIDM()", async () => {
await authenticateWithIDM();
expect(mockSignInWithRedirect).toHaveBeenCalledTimes(1);
});

test("loginUser()", async () => {
await loginUser("[email protected]", "test");
expect(mockSignIn).toHaveBeenCalledTimes(1);
});

test("logoutUser()", async () => {
await logoutUser();
expect(mockSignOut).toHaveBeenCalledTimes(1);
});

test("refreshSession()", async () => {
await refreshSession();
expect(mockSession).toHaveBeenCalledTimes(1);
});

test("del()", async () => {
const test = async () => await del("/del");
await expect(test()).resolves.toBeUndefined();
expect(mockDelete).toHaveBeenCalledTimes(1);
expect(mockTimeout).toHaveBeenCalledTimes(1);
});

test("get()", async () => {
const test = async () => await get<string>("/get");
await expect(test()).resolves.toEqual({ test: "get" });
expect(mockGet).toHaveBeenCalledTimes(1);
expect(mockTimeout).toHaveBeenCalledTimes(1);
});

test("post()", async () => {
const test = async () => await post<string>("/post");
await expect(test()).resolves.toEqual({ test: "post" });
expect(mockPost).toHaveBeenCalledTimes(1);
expect(mockTimeout).toHaveBeenCalledTimes(1);
});

test("put()", async () => {
const test = async () => await put<string>("/put");
await expect(test()).resolves.toEqual({ test: "put" });
expect(mockPut).toHaveBeenCalledTimes(1);
expect(mockTimeout).toHaveBeenCalledTimes(1);
});

test("API error throws with response info", async () => {
jest.spyOn(console, "log").mockImplementation(jest.fn());
const spy = jest.spyOn(console, "log");

mockGet.mockImplementationOnce(() => {
throw {
response: {
body: "Error Info",
},
};
});

await expect(get("/get")).rejects.toThrow(
"Request Failed - /get - Error Info"
);
expect(spy).toHaveBeenCalledTimes(2);
});

test("API error throws without response info", async () => {
jest.spyOn(console, "log").mockImplementation(jest.fn());
const spy = jest.spyOn(console, "log");

mockPost.mockImplementationOnce(() => {
throw "String Error";
});

await expect(post("/post")).rejects.toThrow(
"Request Failed - /post - undefined"
);
expect(spy).toHaveBeenCalledTimes(2);
});
});
109 changes: 109 additions & 0 deletions services/ui-src/src/utils/api/apiLib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* eslint-disable no-console */

import {
del as ampDel,
get as ampGet,
post as ampPost,
put as ampPut,
} from "aws-amplify/api";
import {
AuthTokens,
fetchAuthSession,
signIn,
signInWithRedirect,
signOut,
} from "aws-amplify/auth";
import { updateTimeout } from "utils";

const apiName = "mcr";

interface RequestHeaders {
"x-api-key": string | undefined;
}

interface RequestOptions {
body: any;
}

export async function getRequestHeaders(): Promise<RequestHeaders | undefined> {
try {
const tokens = await getTokens();
const headers = {
"x-api-key": tokens?.idToken?.toString(),
};

return headers;
} catch (error) {
console.log(error);
return;
}
}

export async function getTokens(): Promise<AuthTokens | undefined> {
return (await fetchAuthSession()).tokens;
}

export async function authenticateWithIDM(): Promise<void> {
await signInWithRedirect({ provider: { custom: "Okta" } });
}

export async function loginUser(
username: string,
password: string
): Promise<void> {
await signIn({ username, password });
}

export async function logoutUser(): Promise<void> {
await signOut();
}

export async function refreshSession(): Promise<void> {
await fetchAuthSession({ forceRefresh: true });
}

export async function apiRequest<T>(
request: any,
path: string,
opts?: RequestOptions,
hasResponseBody?: Boolean
): Promise<T> {
const requestHeaders = await getRequestHeaders();
const options = {
headers: { ...requestHeaders },
...opts,
};

try {
await updateTimeout();

if (!hasResponseBody) {
await request({ apiName, path, options }).response;
return undefined as unknown as T;
}

const { body } = await request({ apiName, path, options }).response;
return (await body.json()) as unknown as T;
} catch (e: any) {
const info = `Request Failed - ${path} - ${e.response?.body}`;
console.log(e);
console.log(info);
throw new Error(info);
}
}

export async function del<T>(path: string, opts?: RequestOptions): Promise<T> {
return apiRequest<T>(ampDel, path, opts, false);
}

export async function get<T>(path: string, opts?: RequestOptions): Promise<T> {
return apiRequest<T>(ampGet, path, opts, true);
}

export async function post<T>(path: string, opts?: RequestOptions): Promise<T> {
return apiRequest<T>(ampPost, path, opts, true);
}

export async function put<T>(path: string, opts?: RequestOptions): Promise<T> {
return apiRequest<T>(ampPut, path, opts, true);
}
Loading

0 comments on commit 26e7fb4

Please sign in to comment.