diff --git a/services/ui-src/src/actions/certify.js b/services/ui-src/src/actions/certify.js index df68cd396..6bf9b79a3 100644 --- a/services/ui-src/src/actions/certify.js +++ b/services/ui-src/src/actions/certify.js @@ -1,4 +1,3 @@ -import requestOptions from "../hooks/authHooks/requestOptions"; import { REPORT_STATUS } from "../types"; import { apiLib } from "../util/apiLib"; @@ -19,10 +18,11 @@ export const certifyAndSubmit = () => async (dispatch, getState) => { dispatch({ type: CERTIFY_AND_SUBMIT }); try { - const opts = await requestOptions(); - opts.body = { - status: REPORT_STATUS.certified, - username: username, + const opts = { + body: { + status: REPORT_STATUS.certified, + username: username, + }, }; await apiLib.post(`/state_status/${year}/${state}`, opts); diff --git a/services/ui-src/src/actions/download.js b/services/ui-src/src/actions/download.js index c780712fe..fe6e399f3 100644 --- a/services/ui-src/src/actions/download.js +++ b/services/ui-src/src/actions/download.js @@ -1,4 +1,3 @@ -import requestOptions from "../hooks/authHooks/requestOptions"; import { apiLib } from "../util/apiLib"; export const GET_TEMPLATE = "GET_TEMPLATE"; @@ -8,8 +7,7 @@ export const GET_TEMPLATE_FAILURE = "GET_TEMPLATE_FAILURE"; export const getFiscalYearTemplate = (year) => async (dispatch) => { dispatch({ type: GET_TEMPLATE, data: "" }); try { - const opts = await requestOptions(); - const data = await apiLib.get(`/fiscalYearTemplate/${year}`, opts); + const data = await apiLib.get(`/fiscalYearTemplate/${year}`); dispatch({ type: GET_TEMPLATE_SUCCESS, diff --git a/services/ui-src/src/actions/initial.js b/services/ui-src/src/actions/initial.js index 9e63e69f1..5c340be48 100644 --- a/services/ui-src/src/actions/initial.js +++ b/services/ui-src/src/actions/initial.js @@ -1,5 +1,4 @@ /* eslint-disable no-underscore-dangle, no-console */ -import requestOptions from "../hooks/authHooks/requestOptions"; import { getProgramData, getStateData, getUserData } from "../store/stateUser"; import { apiLib } from "../util/apiLib"; @@ -14,8 +13,7 @@ export const LOAD_LASTYEAR_SECTIONS = "LOAD_LASTYEAR_SECTIONS"; export const getAllStatesData = () => { return async (dispatch) => { try { - const opts = await requestOptions(); - const data = await apiLib.get(`/state`, opts); + const data = await apiLib.get(`/state`); dispatch({ type: GET_ALL_STATES_DATA, data }); } catch (err) { @@ -26,8 +24,7 @@ export const getAllStatesData = () => { }; export const getAllStateStatuses = () => async (dispatch) => { - const opts = await requestOptions(); - const results = await apiLib.get(`/state_status`, opts); + const results = await apiLib.get(`/state_status`); const data = results.Items; const payload = data @@ -71,8 +68,7 @@ export const getAllStateStatuses = () => async (dispatch) => { export const getStateAllStatuses = (selectedYears = [], selectedStates = [], selectedStatus = []) => async (dispatch) => { - const opts = await requestOptions(); - const results = await apiLib.get(`/state_status`, opts); + const results = await apiLib.get(`/state_status`); const data = results.Items; let yearFilter = () => {}; let stateFilter = () => {}; @@ -122,16 +118,12 @@ export const getStateAllStatuses = export const loadSections = ({ stateCode, selectedYear }) => { return async (dispatch) => { - const opts = await requestOptions(); - const data = await apiLib.get( - `/section/${selectedYear}/${stateCode}`, - opts - ); + const data = await apiLib.get(`/section/${selectedYear}/${stateCode}`); const lastYear = parseInt(selectedYear) - 1; let lastYearData = undefined; const priorData = await apiLib - .get(`/section/${lastYear}/${stateCode}`, opts) + .get(`/section/${lastYear}/${stateCode}`) .catch((err) => { console.log("--- ERROR PRIOR YEAR SECTIONS ---"); console.log(err); @@ -152,10 +144,8 @@ export const loadSections = ({ stateCode, selectedYear }) => { export const loadEnrollmentCounts = ({ stateCode, selectedYear }) => { return async (dispatch) => { - const opts = await requestOptions(); const data = await apiLib.get( - `/enrollment_counts/${selectedYear}/${stateCode}`, - opts + `/enrollment_counts/${selectedYear}/${stateCode}` ); dispatch({ type: SET_ENROLLMENT_COUNTS, data }); diff --git a/services/ui-src/src/actions/uncertify.js b/services/ui-src/src/actions/uncertify.js index b977fd44d..f0ceffbd2 100644 --- a/services/ui-src/src/actions/uncertify.js +++ b/services/ui-src/src/actions/uncertify.js @@ -1,4 +1,3 @@ -import requestOptions from "../hooks/authHooks/requestOptions"; import { REPORT_STATUS } from "../types"; import { apiLib } from "../util/apiLib"; @@ -16,12 +15,13 @@ export const uncertifyReport = dispatch({ type: UNCERTIFY }); try { - const opts = await requestOptions(); - opts.body = { - status: REPORT_STATUS.in_progress, - username: username, - year: reportYear, - state: state, + const opts = { + body: { + status: REPORT_STATUS.in_progress, + username: username, + year: reportYear, + state: state, + }, }; await apiLib.post(`/state_status/${year}/${state}`, opts); diff --git a/services/ui-src/src/components/layout/FormTemplates.jsx b/services/ui-src/src/components/layout/FormTemplates.jsx index 08ed1cc98..41a75fe80 100644 --- a/services/ui-src/src/components/layout/FormTemplates.jsx +++ b/services/ui-src/src/components/layout/FormTemplates.jsx @@ -1,4 +1,3 @@ -import requestOptions from "../../hooks/authHooks/requestOptions"; import React, { useState } from "react"; import "react-data-table-component-extensions/dist/index.css"; import { Button } from "@cmsgov/design-system"; @@ -15,7 +14,9 @@ const FormTemplates = () => { setInprogress(true); try { - const opts = await requestOptions({ year: selectedYear }); + const opts = { + body: { year: selectedYear }, + }; await apiLib.post("/formTemplates", opts); window.alert("Request Completed"); history.push("/"); diff --git a/services/ui-src/src/components/layout/FormTemplates.test.jsx b/services/ui-src/src/components/layout/FormTemplates.test.jsx index e3fe279aa..ac03f3b98 100644 --- a/services/ui-src/src/components/layout/FormTemplates.test.jsx +++ b/services/ui-src/src/components/layout/FormTemplates.test.jsx @@ -2,8 +2,7 @@ import React from "react"; import { shallow } from "enzyme"; import FormTemplates from "./FormTemplates"; import { act, render, fireEvent } from "@testing-library/react"; - -const mockAmplifyApi = require("aws-amplify/api"); +import { apiLib } from "../../util/apiLib"; jest.mock("../../hooks/authHooks"); window.alert = jest.fn(); @@ -27,7 +26,7 @@ describe("FormTemplates Component", () => { }); it("fires the generate forms event on button click, then navigates", async () => { - const apiSpy = jest.spyOn(mockAmplifyApi, "post"); + const apiSpy = jest.spyOn(apiLib, "post"); const { getByTestId } = render(formTemplate); const generateButton = getByTestId("generate-forms-button"); await act(async () => { diff --git a/services/ui-src/src/components/sections/Print.jsx b/services/ui-src/src/components/sections/Print.jsx index 1d66fde54..1638f3785 100644 --- a/services/ui-src/src/components/sections/Print.jsx +++ b/services/ui-src/src/components/sections/Print.jsx @@ -11,7 +11,6 @@ import Section from "../layout/Section"; // utils import statesArray from "../utils/statesArray"; import { loadEnrollmentCounts, loadSections } from "../../actions/initial"; -import requestOptions from "../../hooks/authHooks/requestOptions"; import { apiLib } from "../../util/apiLib"; /** @@ -83,9 +82,10 @@ const Print = () => { .replaceAll("\u2014", "-"); const base64String = btoa(unescape(encodeURIComponent(htmlString))); - const opts = await requestOptions(); - opts.body = { - encodedHtml: base64String, + const opts = { + body: { + encodedHtml: base64String, + }, }; const res = await apiLib.post("/print_pdf", opts); diff --git a/services/ui-src/src/components/sections/login/LocalLogins.jsx b/services/ui-src/src/components/sections/login/LocalLogins.jsx index daac4a963..1de2de645 100644 --- a/services/ui-src/src/components/sections/login/LocalLogins.jsx +++ b/services/ui-src/src/components/sections/login/LocalLogins.jsx @@ -1,7 +1,7 @@ import React from "react"; import { useHistory } from "react-router-dom"; -import { signIn } from "aws-amplify/auth"; import { useFormFields } from "../../../hooks/useFormFields"; +import { loginUser } from "../../../util/apiLib"; const LocalLogin = () => { const history = useHistory(); @@ -12,8 +12,8 @@ const LocalLogin = () => { async function handleLogin(event) { event.preventDefault(); try { - await signIn({ username: fields.email, password: fields.password }); - history.push(`/`); + await loginUser(fields.email, fields.password); + history.push("/"); } catch (error) { console.log("Error while logging in.", error); // eslint-disable-line no-console } diff --git a/services/ui-src/src/components/sections/login/LocalLogins.test.jsx b/services/ui-src/src/components/sections/login/LocalLogins.test.jsx index 971fc224a..d676fbe6c 100644 --- a/services/ui-src/src/components/sections/login/LocalLogins.test.jsx +++ b/services/ui-src/src/components/sections/login/LocalLogins.test.jsx @@ -3,10 +3,10 @@ import { shallow } from "enzyme"; import { act, render, fireEvent } from "@testing-library/react"; import { LocalLogins } from "./LocalLogins"; -const mockPost = jest.fn(); const localLogins = ; -jest.mock("aws-amplify/auth", () => ({ - signIn: (credentials) => mockPost(credentials), +const mockLoginUser = jest.fn(); +jest.mock("../../../util/apiLib", () => ({ + loginUser: (username, password) => mockLoginUser(username, password), })); const mockHistoryPush = jest.fn(); jest.mock("react-router-dom", () => ({ @@ -31,15 +31,12 @@ describe("LocalLogin component", () => { fireEvent.change(emailField, { target: { value: "myEmail@email.com" } }); }); await act(async () => { - fireEvent.change(passwordField, { target: { value: "superS3cure" } }); + fireEvent.change(passwordField, { target: { value: "test" } }); }); await act(async () => { fireEvent.click(generateButton); }); - expect(mockPost).toHaveBeenCalledWith({ - username: "myEmail@email.com", - password: "superS3cure", // pragma: allowlist secret - }); + expect(mockLoginUser).toHaveBeenCalledWith("myEmail@email.com", "test"); expect(mockHistoryPush).toHaveBeenCalledWith("/"); }); }); diff --git a/services/ui-src/src/hooks/authHooks/authLifecycle.js b/services/ui-src/src/hooks/authHooks/authLifecycle.js index 348dbf335..0f519e03f 100644 --- a/services/ui-src/src/hooks/authHooks/authLifecycle.js +++ b/services/ui-src/src/hooks/authHooks/authLifecycle.js @@ -1,7 +1,7 @@ -import { fetchAuthSession, signOut } from "aws-amplify/auth"; import { Hub } from "aws-amplify/utils"; import { add } from "date-fns"; import { setAuthTimeout } from "../../store/stateUser"; +import { logoutUser, refreshSession } from "../../util/apiLib"; /* * After the token expires, refresh tokens will be used in the allotted idle window. @@ -28,7 +28,7 @@ class AuthManager { const isExpired = expiration && new Date(expiration).valueOf() < Date.now(); if (isExpired) { localStorage.removeItem("mdctcarts_session_exp"); - signOut().then(() => { + logoutUser().then(() => { window.location.href = "/"; }); } @@ -57,7 +57,7 @@ class AuthManager { * Manual refresh of credentials paired with an instant timer clear */ async refreshCredentials() { - await fetchAuthSession({ forceRefresh: true }); // Force a token refresh + await refreshSession(); this.setTimer(); } @@ -76,7 +76,7 @@ class AuthManager { this.promptTimeout(exp); this.timeoutForceId = setTimeout(() => { localStorage.removeItem("mdctcarts_session_exp"); - signOut(); + logoutUser(); }, IDLE_WINDOW - PROMPT_AT); }, PROMPT_AT, diff --git a/services/ui-src/src/hooks/authHooks/index.js b/services/ui-src/src/hooks/authHooks/index.js index 406092a7a..b9ab9c2cb 100644 --- a/services/ui-src/src/hooks/authHooks/index.js +++ b/services/ui-src/src/hooks/authHooks/index.js @@ -1,4 +1,3 @@ export * from "./useUser"; export * from "./userProvider"; -export * from "./requestOptions"; export * from "./authLifecycle"; diff --git a/services/ui-src/src/hooks/authHooks/requestOptions.js b/services/ui-src/src/hooks/authHooks/requestOptions.js deleted file mode 100644 index a92a2422d..000000000 --- a/services/ui-src/src/hooks/authHooks/requestOptions.js +++ /dev/null @@ -1,18 +0,0 @@ -import { fetchAuthSession } from "aws-amplify/auth"; - -async function requestOptions(body = null) { - try { - const { idToken } = (await fetchAuthSession()).tokens ?? {}; - const options = { - headers: { "x-api-key": idToken?.toString() }, - }; - if (body) { - options["body"] = body; - } - return options; - } catch (e) { - console.log({ e }); // eslint-disable-line no-console - } -} - -export default requestOptions; diff --git a/services/ui-src/src/hooks/authHooks/requestOptions.test.js b/services/ui-src/src/hooks/authHooks/requestOptions.test.js deleted file mode 100644 index 54135a179..000000000 --- a/services/ui-src/src/hooks/authHooks/requestOptions.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import requestOptions from "./requestOptions"; - -jest.mock("aws-amplify/auth", () => ({ - fetchAuthSession: jest.fn().mockReturnValue({ - tokens: { - idToken: "mock token", - }, - }), -})); - -describe("requestOptions", () => { - test("should provide an empty request with an API key header", async () => { - const result = await requestOptions(); - - expect(result).toEqual({ - headers: { - "x-api-key": "mock token", - }, - }); - }); - - test("should decorate requests with an API key header", async () => { - const mockBody = { foo: "bar" }; - const result = await requestOptions(mockBody); - - expect(result).toEqual({ - headers: { - "x-api-key": "mock token", - }, - body: { - foo: "bar", - }, - }); - }); -}); diff --git a/services/ui-src/src/hooks/authHooks/userProvider.jsx b/services/ui-src/src/hooks/authHooks/userProvider.jsx index 2ece50184..21ce02051 100644 --- a/services/ui-src/src/hooks/authHooks/userProvider.jsx +++ b/services/ui-src/src/hooks/authHooks/userProvider.jsx @@ -1,23 +1,15 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useLocation } from "react-router-dom"; -import { - signInWithRedirect, - signOut, - fetchAuthSession, -} from "aws-amplify/auth"; import { UserContext } from "./userContext"; import { AppRoles, IdmRoles } from "../../types"; import { loadUser } from "../../actions/initial"; import { useDispatch } from "react-redux"; import config from "../../config"; +import { authenticateWithIDM, getTokens, logoutUser } from "../../util/apiLib"; const cartsProdDomain = "https://mdctcarts.cms.gov"; const tempEndpoint = "https://dt4brcxdimpa0.cloudfront.net"; -const authenticateWithIDM = async () => { - await signInWithRedirect({ provider: { custom: "Okta" } }); -}; - export const UserProvider = ({ children }) => { const location = useLocation(); const isProduction = @@ -32,7 +24,7 @@ export const UserProvider = ({ children }) => { try { setUser(null); localStorage.removeItem("mdctcarts_session_exp"); - await signOut(); + await logoutUser(); } catch (error) { console.log("error signing out: ", error); // eslint-disable-line no-console } @@ -47,7 +39,7 @@ export const UserProvider = ({ children }) => { // Authenticate try { - const tokens = (await fetchAuthSession()).tokens; + const tokens = await getTokens(); if (!tokens?.idToken) { throw new Error("Missing tokens auth session."); } diff --git a/services/ui-src/src/setupTests.js b/services/ui-src/src/setupTests.js index 460306ed3..5628121f7 100644 --- a/services/ui-src/src/setupTests.js +++ b/services/ui-src/src/setupTests.js @@ -63,11 +63,7 @@ jest.mock("aws-amplify/api", () => ({ }), })), del: jest.fn().mockImplementation(() => ({ - response: Promise.resolve({ - body: { - text: () => Promise.resolve(`{"json":"blob"}`), - }, - }), + response: Promise.resolve({}), })), })); @@ -79,7 +75,7 @@ jest.mock("aws-amplify/auth", () => ({ }), configure: () => {}, signOut: jest.fn().mockImplementation(() => Promise.resolve()), - federatedSignIn: () => {}, + signInWithRedirect: jest.fn(), })); jest.mock("aws-amplify/utils", () => ({ diff --git a/services/ui-src/src/store/saveMiddleware.js b/services/ui-src/src/store/saveMiddleware.js index 51f832a3c..f76af68bb 100644 --- a/services/ui-src/src/store/saveMiddleware.js +++ b/services/ui-src/src/store/saveMiddleware.js @@ -1,5 +1,3 @@ -import requestOptions from "../hooks/authHooks/requestOptions"; - import { QUESTION_ANSWERED } from "../actions/initial"; import { SET_FRAGMENT } from "../actions/repeatables"; import { apiLib } from "../util/apiLib"; @@ -48,8 +46,9 @@ const saveMiddleware = (store) => { try { store.dispatch({ type: SAVE_STARTED }); - const opts = await requestOptions(); - opts.body = store.getState().formData; + const opts = { + body: store.getState().formData, + }; // get the state and year from basic state info section const { stateId, year } = opts.body[0]; await apiLib.put(`/save_report/${year}/${stateId}`, opts); diff --git a/services/ui-src/src/util/apiLib.js b/services/ui-src/src/util/apiLib.js index 7c1e45072..f3f675110 100644 --- a/services/ui-src/src/util/apiLib.js +++ b/services/ui-src/src/util/apiLib.js @@ -1,19 +1,73 @@ /* eslint-disable no-console */ - -import { get, put, post, del } from "aws-amplify/api"; +import { + del as ampDel, + get as ampGet, + post as ampPost, + put as ampPut, +} from "aws-amplify/api"; +import { + fetchAuthSession, + signIn, + signInWithRedirect, + signOut, +} from "aws-amplify/auth"; import { updateTimeout } from "../hooks/authHooks"; +export async function getRequestHeaders() { + 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() { + return (await fetchAuthSession()).tokens; +} + +export async function authenticateWithIDM() { + await signInWithRedirect({ provider: { custom: "Okta" } }); +} + +export async function loginUser(username, password) { + await signIn({ username, password }); +} + +export async function logoutUser() { + await signOut(); +} + +export async function refreshSession() { + await fetchAuthSession({ forceRefresh: true }); // force a token refresh +} + /** * Wrap the AWS API so we can handle any before or after behaviors. * Below we just key off of these API calls as our source of user activity to make sure * credentials don't expire. */ -const apiRequest = async (request, path, options) => { +const apiRequest = async (request, path, opts, hasResponseBody) => { try { - // swallow request error codes for aws lib, they'll kill the lib + const requestHeaders = await getRequestHeaders(); + const options = { + headers: { ...requestHeaders }, + ...opts, + }; await updateTimeout(); - const { body } = await request({ apiName: "carts-api", path, options }) - .response; + const { body, statusCode } = await request({ + apiName: "carts-api", + path, + options, + }).response; + if (hasResponseBody === false || statusCode === 204) { + return undefined; + } const res = await body.text(); // body.json() dies on an empty response, spectacularly return res && res.length > 0 ? JSON.parse(res) : null; } catch (e) { @@ -26,8 +80,8 @@ const apiRequest = async (request, path, options) => { }; export const apiLib = { - post: async (path, options) => await apiRequest(post, path, options), - put: async (path, options) => await apiRequest(put, path, options), - get: async (path, options) => await apiRequest(get, path, options), - del: async (path, options) => await apiRequest(del, path, options), + post: async (path, opts) => await apiRequest(ampPost, path, opts), + put: async (path, opts) => await apiRequest(ampPut, path, opts), + get: async (path, opts) => await apiRequest(ampGet, path, opts), + del: async (path, opts) => await apiRequest(ampDel, path, opts, false), }; diff --git a/services/ui-src/src/util/apiLib.test.js b/services/ui-src/src/util/apiLib.test.js index adb782cdf..78dc6ae2b 100644 --- a/services/ui-src/src/util/apiLib.test.js +++ b/services/ui-src/src/util/apiLib.test.js @@ -1,72 +1,154 @@ -import { apiLib } from "./apiLib"; -import { updateTimeout } from "../hooks/authHooks"; +import { + apiLib, + authenticateWithIDM, + getRequestHeaders, + getTokens, + loginUser, + logoutUser, + refreshSession, +} from "./apiLib"; const mockAmplifyApi = require("aws-amplify/api"); -jest.mock("../hooks/authHooks", () => ({ - updateTimeout: jest.fn(), - initAuthManager: jest.fn(), - refreshCredentials: jest.fn(), +const mockSession = jest.fn(); +const mockSignInWithRedirect = jest.fn(); +const mockSignIn = jest.fn(); +const mockSignOut = jest.fn(); +const mockTimeout = jest.fn(); + +jest.mock("aws-amplify/auth", () => ({ + fetchAuthSession: () => mockSession(), + signIn: () => mockSignIn(), + signOut: () => mockSignOut(), + signInWithRedirect: () => mockSignInWithRedirect(), })); -const path = "my/url"; -const mockOptions = { - headers: { - "x-api-key": "mock key", - }, - body: { - foo: "bar", - }, -}; -const requestObj = { - apiName: "carts-api", - path, - options: mockOptions, -}; +jest.mock("../hooks/authHooks/authLifecycle", () => ({ + updateTimeout: () => mockTimeout(), +})); describe("API lib", () => { 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@address.com", "test"); + expect(mockSignIn).toHaveBeenCalledTimes(1); + }); + + test("logoutUser()", async () => { + await logoutUser(); + expect(mockSignOut).toHaveBeenCalledTimes(1); + }); + + test("refreshSession()", async () => { + await refreshSession(); + expect(mockSession).toHaveBeenCalledTimes(1); + }); + test("Calling post should update the session timeout", async () => { const apiSpy = jest.spyOn(mockAmplifyApi, "post"); - await apiLib.post(path, mockOptions); - - expect(apiSpy).toBeCalledWith(requestObj); - expect(updateTimeout).toBeCalled(); + const test = async () => await apiLib.post("/post"); + await expect(test()).resolves.toEqual({ json: "blob" }); + expect(apiSpy).toHaveBeenCalledTimes(1); + expect(mockTimeout).toHaveBeenCalledTimes(1); }); test("Calling put should update the session timeout", async () => { const apiSpy = jest.spyOn(mockAmplifyApi, "put"); - await apiLib.put(path, mockOptions); - - expect(apiSpy).toBeCalledWith(requestObj); - expect(updateTimeout).toBeCalled(); + const test = async () => await apiLib.put("/put"); + await expect(test()).resolves.toEqual({ json: "blob" }); + expect(apiSpy).toHaveBeenCalledTimes(1); + expect(mockTimeout).toHaveBeenCalledTimes(1); }); test("Calling get should update the session timeout", async () => { const apiSpy = jest.spyOn(mockAmplifyApi, "get"); - await apiLib.get(path, mockOptions); - - expect(apiSpy).toBeCalledWith(requestObj); - expect(updateTimeout).toBeCalled(); + const test = async () => await apiLib.get("/get"); + await expect(test()).resolves.toEqual({ json: "blob" }); + expect(apiSpy).toHaveBeenCalledTimes(1); + expect(mockTimeout).toHaveBeenCalledTimes(1); }); test("Calling del should update the session timeout", async () => { const apiSpy = jest.spyOn(mockAmplifyApi, "del"); - await apiLib.del(path, mockOptions); + const test = async () => await apiLib.del("/del"); + await expect(test()).resolves.toEqual(undefined); + expect(apiSpy).toHaveBeenCalledTimes(1); + expect(mockTimeout).toHaveBeenCalledTimes(1); + }); - expect(apiSpy).toBeCalledWith(requestObj); - expect(updateTimeout).toBeCalled(); + test("API error throws with response info", async () => { + const apiSpy = jest.spyOn(mockAmplifyApi, "get"); + jest.spyOn(console, "log").mockImplementation(jest.fn()); + const spy = jest.spyOn(console, "log"); + + apiSpy.mockImplementationOnce(() => { + throw { + response: { + body: "Error Info", + }, + }; + }); + + await expect(apiLib.get("/get")).rejects.toThrow( + "Request Failed - /get - Error Info" + ); + expect(spy).toHaveBeenCalledTimes(2); }); - test("API errors should be surfaced for handling", async () => { - const apiSpy = jest.spyOn(mockAmplifyApi, "del"); + test("API error throws without response info", async () => { + const apiSpy = jest.spyOn(mockAmplifyApi, "post"); + jest.spyOn(console, "log").mockImplementation(jest.fn()); + const spy = jest.spyOn(console, "log"); + apiSpy.mockImplementationOnce(() => { - throw new Error("500"); + throw "String Error"; }); - await expect(apiLib.del(path, mockOptions)).rejects.toThrow(Error); + await expect(apiLib.post("/post")).rejects.toThrow( + "Request Failed - /post - undefined" + ); + expect(spy).toHaveBeenCalledTimes(2); }); }); diff --git a/services/ui-src/src/util/fileApi.js b/services/ui-src/src/util/fileApi.js index 18a188b25..c706fac02 100644 --- a/services/ui-src/src/util/fileApi.js +++ b/services/ui-src/src/util/fileApi.js @@ -1,5 +1,4 @@ import { apiLib } from "./apiLib"; -import requestOptions from "../hooks/authHooks/requestOptions"; export const recordFileInDatabaseAndGetUploadUrl = async ( year, @@ -12,7 +11,9 @@ export const recordFileInDatabaseAndGetUploadUrl = async ( uploadedFileType: uploadedFile.type, questionId, }; - const opts = await requestOptions(body); + const opts = { + body, + }; const { psurl } = await apiLib.post( `/psUrlUpload/${year}/${stateCode}`, opts @@ -29,7 +30,9 @@ export const uploadFileToS3 = async ({ presignedUploadUrl }, file) => { }; export const getFileDownloadUrl = async (year, stateCode, fileId) => { - const opts = await requestOptions({ fileId }); + const opts = { + body: { fileId }, + }; const response = await apiLib.post( `/psUrlDownload/${year}/${stateCode}`, opts @@ -42,7 +45,9 @@ export const getUploadedFiles = async (year, stateCode, questionId) => { stateCode, questionId, }; - const opts = await requestOptions(body); + const opts = { + body, + }; const response = await apiLib .post(`/uploads/${year}/${stateCode}`, opts) .catch((error) => { @@ -52,10 +57,9 @@ export const getUploadedFiles = async (year, stateCode, questionId) => { }; export const deleteUploadedFile = async (year, stateCode, fileId) => { - const opts = await requestOptions(); const encodedFileId = encodeURIComponent(fileId); await apiLib - .del(`/uploads/${year}/${stateCode}/${encodedFileId}`, opts) + .del(`/uploads/${year}/${stateCode}/${encodedFileId}`) .catch((error) => { console.log("!!!Error retrieving files: ", error); // eslint-disable-line no-console }); diff --git a/services/ui-src/src/util/fileApi.test.js b/services/ui-src/src/util/fileApi.test.js index ae36bd974..244d1b194 100644 --- a/services/ui-src/src/util/fileApi.test.js +++ b/services/ui-src/src/util/fileApi.test.js @@ -16,10 +16,6 @@ jest.mock("./apiLib", () => ({ }, })); -jest.mock("../hooks/authHooks/requestOptions", () => async (body) => ({ - body, -})); - const mockFile = new File(["0xMockDataLOL"], "test.jpg", { type: "image/jpg" }); describe("File API", () => { @@ -109,9 +105,6 @@ describe("File API", () => { await deleteUploadedFile("2023", "AL", "mock-file-id"); - expect(await apiLib.del).toBeCalledWith( - "/uploads/2023/AL/mock-file-id", - {} - ); + expect(await apiLib.del).toBeCalledWith("/uploads/2023/AL/mock-file-id"); }); });