diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5d0925e70f..67238eba1a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -149,7 +149,7 @@ jobs: role-duration-seconds: 10800 - name: Run e2e tests run: run e2e - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report @@ -220,7 +220,7 @@ jobs: join_by() { local IFS="$1"; shift; echo "$*"; } echo "["$(join_by "," "${resourceData[@]}")"]" > "resources/aws-resources.json" - name: Archive stage resources - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: aws-resources-${{ startsWith(github.ref_name, 'snyk-') && 'snyk' || github.ref_name }} path: resources/aws-resources.json diff --git a/lib/lambda/getAllForms.test.ts b/lib/lambda/getAllForms.test.ts index d166b0a0b1..5319fc0bd7 100644 --- a/lib/lambda/getAllForms.test.ts +++ b/lib/lambda/getAllForms.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { getAllForms } from "./getAllForms"; +import * as wfv from "libs/webforms"; vi.mock("../libs/webforms", () => ({ webformVersions: { @@ -12,7 +13,6 @@ vi.mock("../libs/webforms", () => ({ }, }, })); - describe("getAllForms", () => { it("should return a response with status code 200 and the mapped webforms", async () => { const expectedResponse = { @@ -27,4 +27,11 @@ describe("getAllForms", () => { expect(result?.statusCode).toEqual(200); expect(result?.body).toEqual(JSON.stringify(expectedResponse.body)); }); + it("should return a response with status code 200 and the mapped webforms", async () => { + const mockconstant = wfv as { webformVersions: object }; + mockconstant.webformVersions = {}; + + const result = await getAllForms(); + expect(result?.statusCode).toEqual(502); + }); }); diff --git a/lib/lambda/getAllForms.ts b/lib/lambda/getAllForms.ts index 57817087c5..0e2957deb3 100644 --- a/lib/lambda/getAllForms.ts +++ b/lib/lambda/getAllForms.ts @@ -18,12 +18,13 @@ export const getAllForms = async () => { try { const formsWithVersions = mapWebformsKeys(webformVersions); - if (formsWithVersions) { - return response({ - statusCode: 200, - body: formsWithVersions, - }); + if (Object.keys(formsWithVersions).length === 0) { + throw new Error("No form Versions available"); } + return response({ + statusCode: 200, + body: formsWithVersions, + }); } catch (error: any) { console.error("Error:", error); return response({ diff --git a/lib/lambda/getCpocs.test.ts b/lib/lambda/getCpocs.test.ts index c50f172e0b..ee4acdd943 100644 --- a/lib/lambda/getCpocs.test.ts +++ b/lib/lambda/getCpocs.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { APIGatewayEvent } from "aws-lambda"; import { handler } from "./getCpocs"; import { mockedServiceServer } from "mocks/server"; -import { emptyCpocSearchHandler, errorCpocSearchHandler } from "mocks"; +import { emptyOSCpocSearchHandler, errorOSCpocSearchHandler } from "mocks"; import { cpocsList } from "mocks/data/cpocs"; describe("getCpocs Handler", () => { @@ -18,7 +18,7 @@ describe("getCpocs Handler", () => { // TODO - should this be removed? when will the result be empty and not // just a result with an empty hit array it("should return 400 if no Cpocs are found", async () => { - mockedServiceServer.use(emptyCpocSearchHandler); + mockedServiceServer.use(emptyOSCpocSearchHandler); const event = { body: JSON.stringify({}) } as APIGatewayEvent; @@ -39,7 +39,7 @@ describe("getCpocs Handler", () => { }); it("should return 500 if an error occurs during processing", async () => { - mockedServiceServer.use(errorCpocSearchHandler); + mockedServiceServer.use(errorOSCpocSearchHandler); const event = { body: JSON.stringify({}) } as APIGatewayEvent; diff --git a/lib/lambda/getForm.test.ts b/lib/lambda/getForm.test.ts index c64cd0a0e2..5050b27a75 100644 --- a/lib/lambda/getForm.test.ts +++ b/lib/lambda/getForm.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { getForm } from "./getForm"; import { webformVersions } from "libs/webforms"; + describe("forms handler", () => { beforeEach(() => { // Reset mocks before each test @@ -48,4 +49,11 @@ describe("forms handler", () => { expect(result.statusCode).toBe(200); expect(result.body).toBe("{}"); }); + it("returns 502 because the body is invalid json", async () => { + const event = { + body: "kdjfkldjj:[df", + }; + const result = await getForm(event as any); + expect(result.statusCode).toBe(502); + }); }); diff --git a/lib/lambda/getForm.ts b/lib/lambda/getForm.ts index a07c55f3fa..fdf07fa664 100644 --- a/lib/lambda/getForm.ts +++ b/lib/lambda/getForm.ts @@ -40,14 +40,12 @@ export const getForm = async (event: APIGatewayEvent) => { version += getMaxVersion(id); } - if (id && version) { - const formObj = webformVersions[id][version]; - const cleanedForm = convertRegexToString(formObj); - return response({ - statusCode: 200, - body: cleanedForm, - }); - } + const formObj = webformVersions[id][version]; + const cleanedForm = convertRegexToString(formObj); + return response({ + statusCode: 200, + body: cleanedForm, + }); } catch (error: any) { console.error("Error:", error); return response({ @@ -57,12 +55,6 @@ export const getForm = async (event: APIGatewayEvent) => { }, }); } - return response({ - statusCode: 500, - body: { - error: "Internal server error", - }, - }); }; function getMaxVersion(id: string): string { diff --git a/lib/lambda/getPackageActions.test.ts b/lib/lambda/getPackageActions.test.ts index 3d77dbdfce..25e67402ab 100644 --- a/lib/lambda/getPackageActions.test.ts +++ b/lib/lambda/getPackageActions.test.ts @@ -1,10 +1,12 @@ import { APIGatewayEvent } from "aws-lambda"; +import { Action } from "shared-types"; import { getRequestContext } from "mocks"; import { GET_ERROR_ITEM_ID, HI_TEST_ITEM_ID, NOT_FOUND_ITEM_ID, WITHDRAWN_CHANGELOG_ITEM_ID, + INITIAL_RELEASE_APPK_ITEM_ID, } from "mocks/data/items"; import { describe, expect, it } from "vitest"; import { handler } from "./getPackageActions"; @@ -19,6 +21,19 @@ describe("getPackageActions Handler", () => { expect(res.statusCode).toEqual(400); }); + it("should return 500 if event body is invalid", async () => { + const event = { + body: {}, + requestContext: getRequestContext(), + } as APIGatewayEvent; + + const res = await handler(event); + + expect(res).toBeTruthy(); + expect(res.statusCode).toEqual(500); + expect(res.body).toEqual(JSON.stringify({ message: "Internal server error" })); + }); + it("should return 401 if not authorized to view resources from the state", async () => { const event = { body: JSON.stringify({ id: HI_TEST_ITEM_ID }), @@ -47,7 +62,7 @@ describe("getPackageActions Handler", () => { expect(res.body).toEqual(JSON.stringify({ message: "No record found for the given id" })); }); - it("should return 200 with available actions if authorized and package is found", async () => { + it("should return 200 with available actions if authorized and package is found and has no app-k", async () => { const event = { body: JSON.stringify({ id: WITHDRAWN_CHANGELOG_ITEM_ID }), requestContext: getRequestContext(), @@ -60,6 +75,21 @@ describe("getPackageActions Handler", () => { expect(res.body).toEqual(JSON.stringify({ actions: [] })); }); + it("should return 200 with available actions if authorized and package is found and has app-k", async () => { + const event = { + body: JSON.stringify({ id: INITIAL_RELEASE_APPK_ITEM_ID }), + requestContext: getRequestContext(), + } as APIGatewayEvent; + + const res = await handler(event); + + expect(res).toBeTruthy(); + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual( + JSON.stringify({ actions: [Action.WITHDRAW_PACKAGE, Action.UPLOAD_SUBSEQUENT_DOCUMENTS] }), + ); + }); + it("should handle errors during processing", async () => { const event = { body: JSON.stringify({ id: GET_ERROR_ITEM_ID }), diff --git a/lib/lambda/getSubTypes.test.ts b/lib/lambda/getSubTypes.test.ts index abb94ef66b..0662063b13 100644 --- a/lib/lambda/getSubTypes.test.ts +++ b/lib/lambda/getSubTypes.test.ts @@ -12,7 +12,7 @@ import { medicaidSubtypes, chipSubtypes, } from "mocks/data/types"; -import { TestSubtypeItemResult, errorSubtypeSearchHandler } from "mocks"; +import { TestSubtypeItemResult, errorOSSubtypeSearchHandler } from "mocks"; import { mockedServiceServer as mockedServer } from "mocks/server"; describe("getSubTypes Handler", () => { @@ -40,7 +40,7 @@ describe("getSubTypes Handler", () => { }); it("should return 500 if there is a server error", async () => { - mockedServer.use(errorSubtypeSearchHandler); + mockedServer.use(errorOSSubtypeSearchHandler); const event = { body: JSON.stringify({ diff --git a/lib/lambda/getTypes.test.ts b/lib/lambda/getTypes.test.ts index 370a417b82..3ff0e65ff3 100644 --- a/lib/lambda/getTypes.test.ts +++ b/lib/lambda/getTypes.test.ts @@ -8,7 +8,7 @@ import { medicaidTypes, chipTypes, } from "mocks/data/types"; -import { TestTypeItemResult, errorTypeSearchHandler } from "mocks"; +import { TestTypeItemResult, errorOSTypeSearchHandler } from "mocks"; import { mockedServiceServer as mockedServer } from "mocks/server"; describe("getTypes Handler", () => { @@ -33,7 +33,7 @@ describe("getTypes Handler", () => { }); it("should return 500 if there is a server error", async () => { - mockedServer.use(errorTypeSearchHandler); + mockedServer.use(errorOSTypeSearchHandler); const event = { body: JSON.stringify({ authorityId: MEDICAID_SPA_AUTHORITY_ID }), diff --git a/lib/lambda/processEmails.ts b/lib/lambda/processEmails.ts index 0d62cd5e05..29a86325a7 100644 --- a/lib/lambda/processEmails.ts +++ b/lib/lambda/processEmails.ts @@ -175,6 +175,11 @@ export async function processAndSendEmails(record: any, id: string, config: Proc const sec = await getSecret(config.emailAddressLookupSecretName); const item = await os.getItem(config.osDomain, getNamespace("main"), id); + if (!item?.found || !item?._source) { + console.log(`The package was not found for id: ${id}. Doing nothing.`); + return; + } + const cpocEmail = [...getCpocEmail(item)]; const srtEmails = [...getSrtEmails(item)]; diff --git a/lib/lambda/processEmailsHandler.test.ts b/lib/lambda/processEmailsHandler.test.ts index 91017c9649..dace4fd428 100644 --- a/lib/lambda/processEmailsHandler.test.ts +++ b/lib/lambda/processEmailsHandler.test.ts @@ -4,112 +4,206 @@ import { SESClient } from "@aws-sdk/client-ses"; import { handler } from "./processEmails"; import { KafkaRecord, KafkaEvent } from "shared-types"; import { Authority } from "shared-types"; - +import { SIMPLE_ID, WITHDRAW_RAI_ITEM_B, WITHDRAW_RAI_ITEM_C } from "mocks"; const nms = "new-medicaid-submission"; const ncs = "new-chip-submission"; const tempExtension = "temp-extension"; const withdrawPackage = "withdraw-package"; const contractingInitial = "contracting-initial"; const capitatedInitial = "capitated-initial"; +const withdrawRai = "withdraw-rai"; +const respondToRai = "respond-to-rai"; describe("process emails Handler", () => { it.each([ - [`should send an email for ${nms} with ${Authority.MED_SPA}`, Authority.MED_SPA, nms], - [`should send an email for ${nms} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, nms], - [`should send an email for ${nms} with ${Authority["1915b"]}`, Authority["1915b"], nms], - [`should send an email for ${nms} with ${Authority["1915c"]}`, Authority["1915c"], nms], - [`should send an email for ${ncs} with ${Authority.MED_SPA}`, Authority.MED_SPA, ncs], - [`should send an email for ${ncs} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, ncs], - [`should send an email for ${ncs} with ${Authority["1915b"]}`, Authority["1915b"], ncs], - [`should send an email for ${ncs} with ${Authority["1915c"]}`, Authority["1915c"], ncs], + [ + `should send an email for ${respondToRai} with ${Authority.MED_SPA}`, + Authority.MED_SPA, + respondToRai, + SIMPLE_ID, + ], + [ + `should send an email for ${respondToRai} with ${Authority.CHIP_SPA}`, + Authority.CHIP_SPA, + respondToRai, + SIMPLE_ID, + ], + [ + `should send an email for ${respondToRai} with ${Authority["1915b"]}`, + Authority["1915b"], + respondToRai, + SIMPLE_ID, + ], + [ + `should send an email for ${respondToRai} with ${Authority["1915c"]}`, + Authority["1915c"], + respondToRai, + SIMPLE_ID, + ], + [ + `should send an email for ${nms} with ${Authority.MED_SPA}`, + Authority.MED_SPA, + nms, + SIMPLE_ID, + ], + [ + `should send an email for ${nms} with ${Authority.CHIP_SPA}`, + Authority.CHIP_SPA, + nms, + SIMPLE_ID, + ], + [ + `should send an email for ${nms} with ${Authority["1915b"]}`, + Authority["1915b"], + nms, + SIMPLE_ID, + ], + [ + `should send an email for ${nms} with ${Authority["1915c"]}`, + Authority["1915c"], + nms, + SIMPLE_ID, + ], + [ + `should send an email for ${ncs} with ${Authority.MED_SPA}`, + Authority.MED_SPA, + ncs, + SIMPLE_ID, + ], + [ + `should send an email for ${ncs} with ${Authority.CHIP_SPA}`, + Authority.CHIP_SPA, + ncs, + SIMPLE_ID, + ], + [ + `should send an email for ${ncs} with ${Authority["1915b"]}`, + Authority["1915b"], + ncs, + SIMPLE_ID, + ], + [ + `should send an email for ${ncs} with ${Authority["1915c"]}`, + Authority["1915c"], + ncs, + SIMPLE_ID, + ], [ `should send an email for ${tempExtension} with ${Authority.MED_SPA}`, Authority.MED_SPA, tempExtension, + SIMPLE_ID, ], [ `should send an email for ${tempExtension} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, tempExtension, + SIMPLE_ID, ], [ `should send an email for ${tempExtension} with ${Authority["1915b"]}`, Authority["1915b"], tempExtension, + SIMPLE_ID, ], [ `should send an email for ${tempExtension} with ${Authority["1915c"]}`, Authority["1915c"], tempExtension, + SIMPLE_ID, ], [ `should send an email for ${withdrawPackage} with ${Authority.MED_SPA}`, Authority.MED_SPA, withdrawPackage, + SIMPLE_ID, ], [ `should send an email for ${withdrawPackage} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, withdrawPackage, + SIMPLE_ID, ], [ `should send an email for ${withdrawPackage} for ${ncs} with ${Authority["1915b"]}`, Authority["1915b"], withdrawPackage, + SIMPLE_ID, ], [ `should send an email for ${withdrawPackage} with ${Authority["1915c"]}`, Authority["1915c"], withdrawPackage, + SIMPLE_ID, ], [ `should send an email for ${contractingInitial} with ${Authority.MED_SPA}`, Authority.MED_SPA, contractingInitial, + SIMPLE_ID, ], [ `should send an email for ${contractingInitial} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, contractingInitial, + SIMPLE_ID, ], [ `should send an email for ${contractingInitial} with ${Authority["1915b"]}`, Authority["1915b"], contractingInitial, + SIMPLE_ID, ], [ `should send an email for ${contractingInitial} with ${Authority["1915c"]}`, Authority["1915c"], contractingInitial, + SIMPLE_ID, ], [ `should send an email for ${capitatedInitial} with ${Authority.MED_SPA}`, Authority.MED_SPA, capitatedInitial, + SIMPLE_ID, ], [ `should send an email for ${capitatedInitial} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, capitatedInitial, + SIMPLE_ID, ], [ `should send an email for ${capitatedInitial} with ${Authority["1915b"]}`, Authority["1915b"], capitatedInitial, + SIMPLE_ID, ], [ `should send an email for ${capitatedInitial} with ${Authority["1915c"]}`, Authority["1915c"], capitatedInitial, + SIMPLE_ID, ], - ])("%s", async (_, auth, eventType) => { + [ + `should send an email for ${withdrawRai} with ${Authority["1915b"]}`, + Authority["1915b"], + withdrawRai, + WITHDRAW_RAI_ITEM_B, + ], + [ + `should send an email for ${withdrawRai} with ${Authority["1915c"]}`, + Authority["1915c"], + withdrawRai, + WITHDRAW_RAI_ITEM_C, + ], + ])("%s", async (_, auth, eventType, id) => { const callback = vi.fn(); const secSPY = vi.spyOn(SESClient.prototype, "send"); const mockEvent: KafkaEvent = { records: { "mock-topic": [ { - key: Buffer.from("VA").toString("base64"), + key: Buffer.from(id).toString("base64"), value: Buffer.from( JSON.stringify({ origin: "mako", @@ -132,3 +226,45 @@ describe("process emails Handler", () => { expect(secSPY).toHaveBeenCalledTimes(2); }); }); +describe("process emails Handler failures", () => { + it.each([ + [ + `should send an email for ${withdrawRai} with ${Authority["1915b"]} and fail due to not finding it`, + Authority["1915b"], + withdrawRai, + SIMPLE_ID, + ], + [ + `should send an email for ${withdrawRai} with ${Authority["1915c"]} and fail due to not finding it`, + Authority["1915c"], + withdrawRai, + SIMPLE_ID, + ], + ])("%s", async (_, auth, eventType, id = SIMPLE_ID) => { + const callback = vi.fn(); + const mockEvent: KafkaEvent = { + records: { + "mock-topic": [ + { + key: Buffer.from(id).toString("base64"), + value: Buffer.from( + JSON.stringify({ + origin: "mako", + event: eventType, + authority: auth, + }), + ).toString("base64"), + headers: {}, + timestamp: 1732645041557, + offset: "0", + partition: 0, + topic: "mock-topic", + } as unknown as KafkaRecord, + ], + }, + eventSource: "", + bootstrapServers: "", + }; + await expect(() => handler(mockEvent, {} as Context, callback)).rejects.toThrow(); + }); +}); diff --git a/lib/lambda/search.test.ts b/lib/lambda/search.test.ts index 4b49522ca3..02532a03f5 100644 --- a/lib/lambda/search.test.ts +++ b/lib/lambda/search.test.ts @@ -29,7 +29,7 @@ describe("getSearchData Handler", () => { const body = JSON.parse(res.body); expect(body).toBeTruthy(); expect(body?.hits?.hits).toBeTruthy(); - expect(body?.hits?.hits?.length).toEqual(14); + expect(body?.hits?.hits?.length).toEqual(16); }); it("should handle errors during processing", async () => { diff --git a/lib/lambda/sinkCpocs.test.ts b/lib/lambda/sinkCpocs.test.ts index f03453de09..147ef783c6 100644 --- a/lib/lambda/sinkCpocs.test.ts +++ b/lib/lambda/sinkCpocs.test.ts @@ -9,8 +9,12 @@ import { createKafkaRecord, OPENSEARCH_DOMAIN, OPENSEARCH_INDEX_NAMESPACE, + rateLimitBulkUpdateDataHandler, + errorBulkUpdateDataHandler, } from "mocks"; +import { mockedServiceServer as mockedServer } from "mocks/server"; import cpocs, { MUHAMMAD_BASHAR_ID } from "mocks/data/cpocs"; +import { Client } from "@opensearch-project/opensearch"; const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}cpocs`; const TOPIC = "--mako--branch-name--aws.seatool.debezium.cdc.SEA.dbo.Officers"; @@ -221,4 +225,68 @@ describe("test sync cpoc", () => { }, ]); }); + + it("should succeed after receiving a rate limit exceeded error", async () => { + const osBulkSpy = vi.spyOn(Client.prototype, "bulk"); + mockedServer.use(rateLimitBulkUpdateDataHandler); + + await handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + key: MUHAMMAD_BASHAR_KEY, + value: convertObjToBase64({ + payload: { + after: { + Officer_ID: MUHAMMAD_BASHAR_ID, + First_Name: MUHAMMAD_BASHAR._source?.firstName, + Last_Name: MUHAMMAD_BASHAR._source?.lastName, + Email: MUHAMMAD_BASHAR._source?.email, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...MUHAMMAD_BASHAR._source, + }, + ]); + expect(osBulkSpy).toHaveBeenCalledTimes(2); + }); + + it("should succeed after receiving a rate limit exceeded error", async () => { + mockedServer.use(errorBulkUpdateDataHandler); + + await expect(() => + handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + key: MUHAMMAD_BASHAR_KEY, + value: convertObjToBase64({ + payload: { + after: { + Officer_ID: MUHAMMAD_BASHAR_ID, + First_Name: MUHAMMAD_BASHAR._source?.firstName, + Last_Name: MUHAMMAD_BASHAR._source?.lastName, + Email: MUHAMMAD_BASHAR._source?.email, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("Response Error"); + }); }); diff --git a/lib/lambda/sinkInsights.test.ts b/lib/lambda/sinkInsights.test.ts new file mode 100644 index 0000000000..fa2eac3969 --- /dev/null +++ b/lib/lambda/sinkInsights.test.ts @@ -0,0 +1,136 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { handler } from "./sinkInsights"; +import { Context } from "aws-lambda"; +import * as os from "libs/opensearch-lib"; +import * as sink from "../libs/sink-lib"; +import { + convertObjToBase64, + createKafkaEvent, + createKafkaRecord, + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, +} from "mocks"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}insights`; +const TEST_INSIGHT_ID = "42"; +const TEST_INSIGHT_KEY = Buffer.from(TEST_INSIGHT_ID).toString("base64"); +const TOPIC = `--mako--branch-name--aws.seatool.ksql.onemac.three.agg.State_Plan-${TEST_INSIGHT_ID}`; + +describe("test sync types", () => { + const bulkUpdateDataSpy = vi.spyOn(os, "bulkUpdateData"); + const logErrorSpy = vi.spyOn(sink, "logError"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should throw an error if the topic is undefined", async () => { + await expect(() => + handler( + createKafkaEvent({ + undefined: [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (undefined) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should throw an error if the topic is invalid", async () => { + await expect(() => + handler( + createKafkaEvent({ + "invalid-topic": [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (invalid-topic) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should skip if the key is invalid", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + // @ts-expect-error + key: undefined, + value: convertObjToBase64({ + test: "value", + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should skip if the record has no value", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + // @ts-expect-error needs to be undefined for test + value: undefined, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should handle a valid record", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + value: convertObjToBase64({ + test: "value", + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: TEST_INSIGHT_ID, + test: "value", + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/lambda/sinkInsights.ts b/lib/lambda/sinkInsights.ts index 9b3e0c27eb..54726c64a5 100644 --- a/lib/lambda/sinkInsights.ts +++ b/lib/lambda/sinkInsights.ts @@ -9,12 +9,12 @@ export const handler: Handler = async (event) => { for (const topicPartition of Object.keys(event.records)) { const topic = getTopic(topicPartition); switch (topic) { - case undefined: - logError({ type: ErrorType.BADTOPIC }); - throw new Error(); case "aws.seatool.ksql.onemac.three.agg.State_Plan": await ksql(event.records[topicPartition], topicPartition); break; + default: + logError({ type: ErrorType.BADTOPIC }); + throw new Error(`topic (${topicPartition}) is invalid`); } } } catch (error) { @@ -30,7 +30,7 @@ const ksql = async (kafkaRecords: KafkaRecord[], topicPartition: string) => { try { if (!value) continue; - const id: string = JSON.parse(decodeBase64WithUtf8(key)); + const id: string = decodeBase64WithUtf8(key); const record = JSON.parse(decodeBase64WithUtf8(value)); docs.push({ ...record, id }); } catch (error) { diff --git a/lib/lambda/sinkLegacyInsights.test.ts b/lib/lambda/sinkLegacyInsights.test.ts new file mode 100644 index 0000000000..ce223ee340 --- /dev/null +++ b/lib/lambda/sinkLegacyInsights.test.ts @@ -0,0 +1,210 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { handler } from "./sinkLegacyInsights"; +import { Context } from "aws-lambda"; +import * as os from "libs/opensearch-lib"; +import * as sink from "../libs/sink-lib"; +import { + convertObjToBase64, + createKafkaEvent, + createKafkaRecord, + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, +} from "mocks"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}legacyinsights`; +const TEST_INSIGHT_ID = "42"; +const TEST_INSIGHT_KEY = Buffer.from(TEST_INSIGHT_ID).toString("base64"); +const TOPIC = `--mako--branch-name--aws.onemac.migration.cdc-${TEST_INSIGHT_ID}`; + +describe("test sync types", () => { + const bulkUpdateDataSpy = vi.spyOn(os, "bulkUpdateData"); + const logErrorSpy = vi.spyOn(sink, "logError"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should throw an error if the topic is undefined", async () => { + await expect(() => + handler( + createKafkaEvent({ + undefined: [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (undefined) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should throw an error if the topic is invalid", async () => { + await expect(() => + handler( + createKafkaEvent({ + "invalid-topic": [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (invalid-topic) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should skip if the key is invalid", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + // @ts-expect-error + key: undefined, + value: convertObjToBase64({ + test: "value", + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should delete the record if the value is undefined", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + // @ts-expect-error needs to be undefined for test + value: undefined, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: TEST_INSIGHT_ID, + hardDeletedFromLegacy: true, + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should skip if the record does not have sk field", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + value: convertObjToBase64({ + test: "value", + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should handle a valid record for package change", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + value: convertObjToBase64({ + test: "value", + sk: "Package", + }), + offset: 0, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: TEST_INSIGHT_ID, + test: "value", + sk: "Package", + approvedEffectiveDate: null, + changedDate: null, + finalDispositionDate: null, + proposedDate: null, + proposedEffectiveDate: null, + statusDate: null, + submissionDate: null, + hardDeletedFromLegacy: null, + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should handle a valid record for offset change", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + value: convertObjToBase64({ + test: "value", + sk: "Offset", + }), + offset: 3, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: "3", + test: "value", + sk: "Offset", + approvedEffectiveDate: null, + changedDate: null, + finalDispositionDate: null, + proposedDate: null, + proposedEffectiveDate: null, + statusDate: null, + submissionDate: null, + hardDeletedFromLegacy: null, + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/lambda/sinkLegacyInsights.ts b/lib/lambda/sinkLegacyInsights.ts index 023238f32a..913908b997 100644 --- a/lib/lambda/sinkLegacyInsights.ts +++ b/lib/lambda/sinkLegacyInsights.ts @@ -9,12 +9,12 @@ export const handler: Handler = async (event) => { for (const topicPartition of Object.keys(event.records)) { const topic = getTopic(topicPartition); switch (topic) { - case undefined: - logError({ type: ErrorType.BADTOPIC }); - throw new Error(); case "aws.onemac.migration.cdc": await onemac(event.records[topicPartition], topicPartition); break; + default: + logError({ type: ErrorType.BADTOPIC }); + throw new Error(`topic (${topicPartition}) is invalid`); } } } catch (error) { diff --git a/lib/lambda/sinkMainProcessors.test.ts b/lib/lambda/sinkMainProcessors.test.ts index c4b05ab5a9..89f011bfcd 100644 --- a/lib/lambda/sinkMainProcessors.test.ts +++ b/lib/lambda/sinkMainProcessors.test.ts @@ -14,9 +14,12 @@ import { OPENSEARCH_INDEX_NAMESPACE, TEST_ITEM_ID, EXISTING_ITEM_TEMPORARY_EXTENSION_ID, + NOT_FOUND_ITEM_ID, convertObjToBase64, createKafkaRecord, + errorOSMainMultiDocumentHandler, } from "mocks"; +import { mockedServiceServer as mockedServer } from "mocks/server"; import { appkBase, capitatedInitial, @@ -615,6 +618,119 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { ]); }); + it("outputs kafka records into mako records if mako record is not found", async () => { + await insertNewSeatoolRecordsFromKafkaIntoMako( + [ + createKafkaRecord({ + topic: TOPIC, + key: Buffer.from(NOT_FOUND_ITEM_ID).toString("base64"), + value: convertObjToBase64({ + id: NOT_FOUND_ITEM_ID, + ACTION_OFFICERS: [ + { + FIRST_NAME: "John", + LAST_NAME: "Doe", + EMAIL: "john.doe@medicaid.gov", + OFFICER_ID: 12345, + DEPARTMENT: "State Plan Review", + PHONE: "202-555-1234", + }, + { + FIRST_NAME: "Emily", + LAST_NAME: "Rodriguez", + EMAIL: "emily.rodriguez@medicaid.gov", + OFFICER_ID: 12346, + DEPARTMENT: "Compliance Division", + PHONE: "202-555-5678", + }, + ], + LEAD_ANALYST: [ + { + FIRST_NAME: "Michael", + LAST_NAME: "Chen", + EMAIL: "michael.chen@cms.hhs.gov", + OFFICER_ID: 67890, + DEPARTMENT: "Medicaid Innovation Center", + PHONE: "202-555-9012", + }, + ], + STATE_PLAN: { + PLAN_TYPE: 123, + SPW_STATUS_ID: 4, + APPROVED_EFFECTIVE_DATE: TIMESTAMP, + CHANGED_DATE: EARLIER_TIMESTAMP, + SUMMARY_MEMO: "Sample summary", + TITLE_NAME: "Sample Title", + STATUS_DATE: EARLIER_TIMESTAMP, + SUBMISSION_DATE: TIMESTAMP, + LEAD_ANALYST_ID: 67890, + ACTUAL_EFFECTIVE_DATE: null, + PROPOSED_DATE: null, + STATE_CODE: "10", + }, + RAI: [], + ACTIONTYPES: [{ ACTION_NAME: "Initial Review", ACTION_ID: 1, PLAN_TYPE_ID: 123 }], + STATE_PLAN_SERVICETYPES: [{ SPA_TYPE_ID: 1, SPA_TYPE_NAME: "Type A" }], + STATE_PLAN_SERVICE_SUBTYPES: [{ TYPE_ID: 1, TYPE_NAME: "SubType X" }], + }), + }), + ], + TOPIC, + ); + + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + actionType: "Initial Review", + approvedEffectiveDate: ISO_DATETIME, + authority: "1915(c)", + changed_date: EARLIER_TIMESTAMP, + cmsStatus: "Approved", + description: "Sample summary", + finalDispositionDate: EARLIER_ISO_DATETIME, + id: NOT_FOUND_ITEM_ID, + initialIntakeNeeded: false, + leadAnalystEmail: "michael.chen@cms.hhs.gov", + leadAnalystName: "Michael Chen", + leadAnalystOfficerId: 67890, + locked: false, + proposedDate: null, + raiReceivedDate: null, + raiRequestedDate: null, + raiWithdrawEnabled: false, + raiWithdrawnDate: null, + reviewTeam: [ + { + email: "john.doe@medicaid.gov", + name: "John Doe", + }, + { + email: "emily.rodriguez@medicaid.gov", + name: "Emily Rodriguez", + }, + ], + seatoolStatus: "Approved", + secondClock: false, + state: "10", + stateStatus: "Approved", + statusDate: EARLIER_ISO_DATETIME, + subTypes: [ + { + TYPE_ID: 1, + TYPE_NAME: "SubType X", + }, + ], + subject: "Sample Title", + submissionDate: ISO_DATETIME, + types: [ + { + SPA_TYPE_ID: 1, + SPA_TYPE_NAME: "Type A", + }, + ], + }, + ]); + }); + it("outputs kafka records into mako records without changedDates", async () => { await insertNewSeatoolRecordsFromKafkaIntoMako( [ @@ -728,6 +844,121 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { ]); }); + it("handles errors in getting mako timestamps", async () => { + mockedServer.use(errorOSMainMultiDocumentHandler); + + await insertNewSeatoolRecordsFromKafkaIntoMako( + [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_ID, + ACTION_OFFICERS: [ + { + FIRST_NAME: "John", + LAST_NAME: "Doe", + EMAIL: "john.doe@medicaid.gov", + OFFICER_ID: 12345, + DEPARTMENT: "State Plan Review", + PHONE: "202-555-1234", + }, + { + FIRST_NAME: "Emily", + LAST_NAME: "Rodriguez", + EMAIL: "emily.rodriguez@medicaid.gov", + OFFICER_ID: 12346, + DEPARTMENT: "Compliance Division", + PHONE: "202-555-5678", + }, + ], + LEAD_ANALYST: [ + { + FIRST_NAME: "Michael", + LAST_NAME: "Chen", + EMAIL: "michael.chen@cms.hhs.gov", + OFFICER_ID: 67890, + DEPARTMENT: "Medicaid Innovation Center", + PHONE: "202-555-9012", + }, + ], + STATE_PLAN: { + PLAN_TYPE: 123, + SPW_STATUS_ID: 4, + APPROVED_EFFECTIVE_DATE: TIMESTAMP, + CHANGED_DATE: EARLIER_TIMESTAMP, + SUMMARY_MEMO: "Sample summary", + TITLE_NAME: "Sample Title", + STATUS_DATE: EARLIER_TIMESTAMP, + SUBMISSION_DATE: TIMESTAMP, + LEAD_ANALYST_ID: 67890, + ACTUAL_EFFECTIVE_DATE: null, + PROPOSED_DATE: null, + STATE_CODE: "10", + }, + RAI: [], + ACTIONTYPES: [{ ACTION_NAME: "Initial Review", ACTION_ID: 1, PLAN_TYPE_ID: 123 }], + STATE_PLAN_SERVICETYPES: [{ SPA_TYPE_ID: 1, SPA_TYPE_NAME: "Type A" }], + STATE_PLAN_SERVICE_SUBTYPES: [{ TYPE_ID: 1, TYPE_NAME: "SubType X" }], + }), + }), + ], + TOPIC, + ); + + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + actionType: "Initial Review", + approvedEffectiveDate: ISO_DATETIME, + authority: "1915(c)", + changed_date: EARLIER_TIMESTAMP, + cmsStatus: "Approved", + description: "Sample summary", + finalDispositionDate: EARLIER_ISO_DATETIME, + id: TEST_ITEM_ID, + initialIntakeNeeded: false, + leadAnalystEmail: "michael.chen@cms.hhs.gov", + leadAnalystName: "Michael Chen", + leadAnalystOfficerId: 67890, + locked: false, + proposedDate: null, + raiReceivedDate: null, + raiRequestedDate: null, + raiWithdrawEnabled: false, + raiWithdrawnDate: null, + reviewTeam: [ + { + email: "john.doe@medicaid.gov", + name: "John Doe", + }, + { + email: "emily.rodriguez@medicaid.gov", + name: "Emily Rodriguez", + }, + ], + seatoolStatus: "Approved", + secondClock: false, + state: "10", + stateStatus: "Approved", + statusDate: EARLIER_ISO_DATETIME, + subTypes: [ + { + TYPE_ID: 1, + TYPE_NAME: "SubType X", + }, + ], + subject: "Sample Title", + submissionDate: ISO_DATETIME, + types: [ + { + SPA_TYPE_ID: 1, + SPA_TYPE_NAME: "Type A", + }, + ], + }, + ]); + }); + it("skips newer mako records", async () => { await insertNewSeatoolRecordsFromKafkaIntoMako( [ diff --git a/lib/lambda/submit/submissionPayloads/respond-to-rai.ts b/lib/lambda/submit/submissionPayloads/respond-to-rai.ts index 0d1827e539..3fd5ca4a4a 100644 --- a/lib/lambda/submit/submissionPayloads/respond-to-rai.ts +++ b/lib/lambda/submit/submissionPayloads/respond-to-rai.ts @@ -31,7 +31,7 @@ export const respondToRai = async (event: APIGatewayEvent) => { const transformedData = events["respond-to-rai"].schema.parse({ ...parsedResult.data, - authority: item?._source.authority, + authority: item?._source?.authority, submitterName, submitterEmail, timestamp: Date.now(), diff --git a/lib/lambda/update/updatePackage.test.ts b/lib/lambda/update/updatePackage.test.ts index 89e4514a6d..c95f792f29 100644 --- a/lib/lambda/update/updatePackage.test.ts +++ b/lib/lambda/update/updatePackage.test.ts @@ -7,7 +7,7 @@ import { EXISTING_ITEM_PENDING_ID, CAPITATED_INITIAL_ITEM_ID, CAPITATED_INITIAL_NEW_ITEM_ID, - WEIRD_ID, + SIMPLE_ID, } from "mocks"; vi.mock("libs/handler-lib", () => ({ response: vi.fn((data) => data), @@ -204,7 +204,7 @@ describe("handler", () => { it("should fail to update a package with bad existing id format", async () => { const noActionevent = { body: JSON.stringify({ - packageId: WEIRD_ID, + packageId: SIMPLE_ID, action: "update-id", changeReason: "Nunya", updatedId: "SS-120", @@ -212,15 +212,18 @@ describe("handler", () => { } as APIGatewayEvent; const result = await handler(noActionevent); - const expectedResult = "Cannot read properties of undefined (reading 'baseSchema')"; + expect(result?.statusCode).toStrictEqual(500); - expect(result?.body.message).toStrictEqual(expectedResult); + expect(result?.body).toStrictEqual({ + message: "Cannot read properties of undefined (reading 'baseSchema')", + }); }); + it("should fail to update a package - no topic name ", async () => { process.env.topicName = ""; const noActionevent = { body: JSON.stringify({ - packageId: WEIRD_ID, + packageId: SIMPLE_ID, action: "update-values", changeReason: "Nunya", updatedFields: {}, @@ -237,7 +240,7 @@ describe("handler", () => { it("should fail to update a package - No valid fields ", async () => { const noActionevent = { body: JSON.stringify({ - packageId: WEIRD_ID, + packageId: SIMPLE_ID, action: "update-values", changeReason: "Nunya", updatedFields: { badfield: "nothing" }, @@ -254,7 +257,7 @@ describe("handler", () => { it("should fail to update a package - Id can not be updated ", async () => { const noActionevent = { body: JSON.stringify({ - packageId: WEIRD_ID, + packageId: SIMPLE_ID, action: "update-values", changeReason: "Nunya", updatedFields: { id: "cant update ID here" }, diff --git a/lib/libs/api/package/appk.test.ts b/lib/libs/api/package/appk.test.ts index 64717451b0..8bfe427eb2 100644 --- a/lib/libs/api/package/appk.test.ts +++ b/lib/libs/api/package/appk.test.ts @@ -33,8 +33,10 @@ describe("getAppkChildren", () => { hits: [ { _source: { + authority: "1915(c)", changedDate: "2024-01-01T00:00:00Z", title: "Initial release", + seatoolStatus: "Pending", cmsStatus: "Pending", stateStatus: "Under Review", }, diff --git a/lib/libs/api/package/itemExists.ts b/lib/libs/api/package/itemExists.ts index c5172f161b..0610657f7d 100644 --- a/lib/libs/api/package/itemExists.ts +++ b/lib/libs/api/package/itemExists.ts @@ -14,7 +14,7 @@ export async function itemExists(params: { : getNamespace("main"); const packageResult = await os.getItem(domain, index, params.id); - return !!packageResult?._source; + return packageResult?._source !== undefined && packageResult?._source !== null; } catch (error) { console.error(error); return false; diff --git a/lib/libs/email/content/withdrawRai/index.tsx b/lib/libs/email/content/withdrawRai/index.tsx index 5dde78064f..a8e34e6a48 100644 --- a/lib/libs/email/content/withdrawRai/index.tsx +++ b/lib/libs/email/content/withdrawRai/index.tsx @@ -5,7 +5,7 @@ import { render } from "@react-email/render"; import { EmailProcessingError } from "libs/email/errors"; const getWithdrawRaiEvent = async (id: string) => { - const event = await getLatestMatchingEvent(id, "WithdrawRai"); + const event = await getLatestMatchingEvent(id, "withdraw-rai"); if (!event) { return null; diff --git a/lib/libs/email/index.ts b/lib/libs/email/index.ts index da2eea2fff..ca21f9e4dd 100644 --- a/lib/libs/email/index.ts +++ b/lib/libs/email/index.ts @@ -115,7 +115,7 @@ export async function getLatestMatchingEvent( } // Filter matching events - const events = item.hits.hits.filter((event) => event._source.actionType === actionType); + const events = item.hits.hits.filter((event) => event._source.event === actionType); // Check if any matching events were found if (!events.length) { diff --git a/lib/libs/email/vitest.setup.ts b/lib/libs/email/vitest.setup.ts index bfc8e2a7eb..a3a0e206d9 100644 --- a/lib/libs/email/vitest.setup.ts +++ b/lib/libs/email/vitest.setup.ts @@ -1,6 +1,6 @@ import { afterAll, afterEach, beforeAll, beforeEach, vi } from "vitest"; -import { mockedServiceServer as mockedServer } from "mocks/server"; import { REGION, setDefaultStateSubmitter } from "mocks"; +import { mockedServiceServer as mockedServer } from "mocks/server"; beforeAll(() => { setDefaultStateSubmitter(); diff --git a/lib/libs/handler-lib.ts b/lib/libs/handler-lib.ts index 5eb9dc0035..7a13363ca3 100644 --- a/lib/libs/handler-lib.ts +++ b/lib/libs/handler-lib.ts @@ -1,21 +1,3 @@ -import type { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda"; - -export const handler = async ( - handler: (event?: APIGatewayEvent, context?: Context) => Promise, -) => { - const handlerResponse = await handler(); - - const response: APIGatewayProxyResult = { - headers: { - "Access-Control-Allow-Headers": "Content-Type", - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "OPTIONS,POST,GET,PUT,DELETE", - }, - ...handlerResponse, - }; - return () => response; -}; - export function response( currentResponse: { statusCode?: number; diff --git a/lib/libs/opensearch-lib.test.ts b/lib/libs/opensearch-lib.test.ts new file mode 100644 index 0000000000..791caccbcb --- /dev/null +++ b/lib/libs/opensearch-lib.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect, vi } from "vitest"; +import { updateFieldMapping, decodeUtf8 } from "./opensearch-lib"; +import { + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, + errorUpdateFieldMappingHandler, +} from "mocks"; +import { mockedServiceServer as mockedServer } from "mocks/server"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}main`; + +describe("opensearch-lib tests", () => { + describe("updateFieldMapping tests", () => { + it("should handle a server error when updating a field mapping", async () => { + mockedServer.use(errorUpdateFieldMappingHandler); + + await expect(() => + updateFieldMapping(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, { + throw: "error", + }), + ).rejects.toThrowError("Response Error"); + }); + }); + + describe("decodeUtf8 tests", () => { + it("should handle decoding an invalid string", () => { + vi.stubGlobal("decodeURIComponent", () => { + throw new Error("Bad format"); + }); + + const value = "test%20value%%"; + const decoded = decodeUtf8(value); + expect(decoded).toEqual(value); + + vi.unstubAllGlobals(); + }); + }); +}); diff --git a/lib/libs/opensearch-lib.ts b/lib/libs/opensearch-lib.ts index 521b17d969..9308162fe4 100644 --- a/lib/libs/opensearch-lib.ts +++ b/lib/libs/opensearch-lib.ts @@ -183,7 +183,11 @@ export async function getItem( try { client = client || (await getClient(host)); const response = await client.get({ id, index }); - return decodeUtf8(response).body; + const item = decodeUtf8(response).body; + if (item.found === false || !item._source) { + return undefined; + } + return item; } catch (error) { if ( (error instanceof OpensearchErrors.ResponseError && error.statusCode === 404) || @@ -210,18 +214,12 @@ export async function getItems(ids: string[]): Promise { }); return response.body.docs.reduce((acc, doc) => { - if (doc.found && doc._source) { - try { - return acc.concat(doc._source); - } catch (e) { - console.error(`Failed to parse JSON for document with ID ${doc._id}:`, e); - return acc; - } + if (doc && doc.found && doc._source) { + return acc.concat(doc._source); } else { console.error(`Document with ID ${doc._id} not found.`); + return acc; } - - return acc; }, []); } catch (e) { console.log({ e }); @@ -264,7 +262,7 @@ export async function updateFieldMapping( } } -function decodeUtf8(data: any): any { +export function decodeUtf8(data: any): any { if (typeof data === "string") { try { return decodeURIComponent(escape(data)); diff --git a/lib/packages/shared-utils/package-actions/getAvailableActions.test.ts b/lib/packages/shared-utils/package-actions/getAvailableActions.test.ts new file mode 100644 index 0000000000..6633604377 --- /dev/null +++ b/lib/packages/shared-utils/package-actions/getAvailableActions.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect } from "vitest"; +import { getAvailableActions } from "./getAvailableActions"; +import { Action, SEATOOL_STATUS } from "shared-types"; +import { + TEST_1915B_ITEM, + TEST_CHIP_SPA_ITEM, + TEST_CMS_REVIEWER_USER, + TEST_MED_SPA_ITEM, + TEST_STATE_SUBMITTER_USER, +} from "mocks"; + +describe("getAvailableActions tests", () => { + it(`should return actions: [${Action.RESPOND_TO_RAI},${Action.WITHDRAW_PACKAGE}]`, () => { + const result = getAvailableActions(TEST_STATE_SUBMITTER_USER, { + ...TEST_MED_SPA_ITEM._source, + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + raiRequestedDate: "2024-01-01T00:00:00.000Z", + }); + expect(result).toEqual([Action.RESPOND_TO_RAI, Action.WITHDRAW_PACKAGE]); + }); + + it(`should return actions: [${Action.TEMP_EXTENSION}, ${Action.AMEND_WAIVER}]`, () => { + const result = getAvailableActions(TEST_STATE_SUBMITTER_USER, { + ...TEST_1915B_ITEM._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.APPROVED, + }); + expect(result).toEqual([Action.TEMP_EXTENSION, Action.AMEND_WAIVER]); + }); + + it(`should return actions: [${Action.ENABLE_RAI_WITHDRAW}] for CHIP SPA`, () => { + const result = getAvailableActions(TEST_CMS_REVIEWER_USER, { + ...TEST_CHIP_SPA_ITEM._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(result).toEqual([Action.ENABLE_RAI_WITHDRAW]); + }); + + it(`should return actions: [${Action.ENABLE_RAI_WITHDRAW}] for Medicaid SPA`, () => { + const result = getAvailableActions(TEST_CMS_REVIEWER_USER, { + ...TEST_MED_SPA_ITEM._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(result).toEqual([Action.ENABLE_RAI_WITHDRAW]); + }); + + it(`should return actions: [${Action.DISABLE_RAI_WITHDRAW}] for CHIP SPA`, () => { + const result = getAvailableActions(TEST_CMS_REVIEWER_USER, { + ...TEST_CHIP_SPA_ITEM._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(result).toEqual([Action.DISABLE_RAI_WITHDRAW]); + }); + + it(`should return actions: [${Action.DISABLE_RAI_WITHDRAW}] for Medicaid SPA`, () => { + const result = getAvailableActions(TEST_CMS_REVIEWER_USER, { + ...TEST_MED_SPA_ITEM._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(result).toEqual([Action.DISABLE_RAI_WITHDRAW]); + }); + + it(`should return actions: [${Action.WITHDRAW_RAI}, ${Action.WITHDRAW_PACKAGE}, ${Action.UPLOAD_SUBSEQUENT_DOCUMENTS}]`, () => { + const result = getAvailableActions(TEST_STATE_SUBMITTER_USER, { + ...TEST_MED_SPA_ITEM._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(result).toEqual([ + Action.WITHDRAW_RAI, + Action.WITHDRAW_PACKAGE, + Action.UPLOAD_SUBSEQUENT_DOCUMENTS, + ]); + }); + + it(`should return actions: [${Action.WITHDRAW_PACKAGE}, ${Action.UPLOAD_SUBSEQUENT_DOCUMENTS}]`, () => { + const result = getAvailableActions(TEST_STATE_SUBMITTER_USER, { + ...TEST_MED_SPA_ITEM._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiWithdrawEnabled: true, + }); + expect(result).toEqual([Action.WITHDRAW_PACKAGE, Action.UPLOAD_SUBSEQUENT_DOCUMENTS]); + }); +}); diff --git a/lib/packages/shared-utils/package-actions/getAvailableActions.ts b/lib/packages/shared-utils/package-actions/getAvailableActions.ts index c6a21a755f..346a4a11ca 100644 --- a/lib/packages/shared-utils/package-actions/getAvailableActions.ts +++ b/lib/packages/shared-utils/package-actions/getAvailableActions.ts @@ -1,4 +1,4 @@ -import { Action, CognitoUserAttributes, opensearch } from "../../shared-types"; +import { Action, CognitoUserAttributes, opensearch } from "shared-types"; import { PackageCheck } from "../package-check"; import rules from "./rules"; diff --git a/lib/packages/shared-utils/package-actions/rules.test.ts b/lib/packages/shared-utils/package-actions/rules.test.ts new file mode 100644 index 0000000000..7296ea6c5f --- /dev/null +++ b/lib/packages/shared-utils/package-actions/rules.test.ts @@ -0,0 +1,697 @@ +import { describe, expect, it } from "vitest"; +import { + arRespondToRai, + arTempExtension, + arAmend, + arEnableWithdrawRaiResponse, + arDisableWithdrawRaiResponse, + arWithdrawRaiResponse, + arWithdrawPackage, + arUploadSubsequentDocuments, +} from "./rules"; +import { PackageCheck } from "../package-check"; +import { SEATOOL_STATUS } from "shared-types"; +import { + TEST_MED_SPA_ITEM, + TEST_TEMP_EXT_ITEM, + TEST_CHIP_SPA_ITEM, + TEST_1915B_ITEM, + TEST_1915C_ITEM, + TEST_STATE_SUBMITTER_USER, + TEST_CMS_REVIEWER_USER, +} from "mocks"; + +describe("package actions rules tests", () => { + describe("arRespondToRai rule tests", () => { + it("should return true for a valid package", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + raiRequestedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arRespondToRai.check(check, TEST_STATE_SUBMITTER_USER)).toBe(true); + }); + it("should return false for a temporary extension package", () => { + const check = PackageCheck({ + ...TEST_TEMP_EXT_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + raiRequestedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arRespondToRai.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with a non Pending-RAI status", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + raiRequestedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arRespondToRai.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package without a raiRequestedDate", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + raiRequestedDate: null, + }); + expect(arRespondToRai.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with an rai response", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arRespondToRai.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with a cms reviewer user", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + raiRequestedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arRespondToRai.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package that is locked", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + raiRequestedDate: "2024-01-01T00:00:00.000Z", + locked: true, + }); + expect(arRespondToRai.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + }); + + describe("arTempExtension rule tests", () => { + it("should return true for a valid 1915(b) package", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "New", + }); + expect(arTempExtension.check(check, TEST_STATE_SUBMITTER_USER)).toBe(true); + }); + it("should return true for a valid 1915(c) package", () => { + const check = PackageCheck({ + ...TEST_1915C_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "New", + }); + expect(arTempExtension.check(check, TEST_STATE_SUBMITTER_USER)).toBe(true); + }); + it("should return false for a package with a non-Approved status", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + }); + expect(arTempExtension.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a Medicaid SPA package", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "New", + }); + expect(arTempExtension.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a CHIP SPA package", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "New", + }); + expect(arTempExtension.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with action type Extend", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "Extend", + }); + expect(arTempExtension.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with a cms reviewer user", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "New", + }); + expect(arTempExtension.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + }); + + describe("arAmend rule tests", () => { + it("should return true for a valid 1915(b) package", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "Renew", + }); + expect(arAmend.check(check, TEST_STATE_SUBMITTER_USER)).toBe(true); + }); + + it("should return true for a valid 1915(c) package", () => { + const check = PackageCheck({ + ...TEST_1915C_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "Renew", + }); + expect(arAmend.check(check, TEST_STATE_SUBMITTER_USER)).toBe(true); + }); + + it("should return false for a Medicaid SPA package", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "Renew", + }); + expect(arAmend.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + + it("should return false for a CHIP SPA package", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "Renew", + }); + expect(arAmend.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + + it("should return false for a package with a non-Approved status", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "Renew", + }); + expect(arAmend.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + + it("should return false for a package with an actionType of Extend", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "Extend", + }); + expect(arAmend.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + + it("should return false for a package with a cms reviewer", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "Renew", + }); + expect(arAmend.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + }); + + describe("arEnableWithdrawRaiResponse rule tests", () => { + it("should return true for a valid package CHIP SPA", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(true); + }); + it("should return false for a temporary extension package CHIP SPA", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "Extend", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a Withdrawn status CHIP SPA", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.WITHDRAWN, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package that has an rai response CHIP SPA", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawnDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with an rai withdraw enabled CHIP SPA", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a state submitter CHIP SPA", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with an Approved status CHIP SPA", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a Pending-Approved status CHIP SPA", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_APPROVAL, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a Submitted status CHIP SPA", () => { + const check = PackageCheck({ + ...TEST_CHIP_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + + it("should return true for a valid package 1915(b)", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(true); + }); + it("should return false for a temporary extension package 1915(b)", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "Extend", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a Withdrawn status 1915(b)", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.WITHDRAWN, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package that has an rai response 1915(b)", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawnDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with an rai withdraw enabled 1915(b)", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a state submitter 1915(b)", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with an Approved status 1915(b)", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a Pending-Approved status 1915(b)", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_APPROVAL, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a Terminated status 1915(b)", () => { + const check = PackageCheck({ + ...TEST_1915B_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.TERMINATED, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arEnableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + }); + + describe("arDisableWithdrawRaiResponse rule tests", () => { + it("should return true for a valid package", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arDisableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(true); + }); + it("should return false for a temporary extension package", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "Extend", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arDisableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a Withdrawn status", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.WITHDRAWN, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arDisableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package that has an rai response", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawnDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arDisableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with an rai withdraw enabled", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: false, + }); + expect(arDisableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a state submitter", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arDisableWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with an Approved status", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.APPROVED, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arDisableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a Pending-Approved status", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_APPROVAL, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arDisableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package with a Unsubmitted status", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.UNSUBMITTED, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arDisableWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + }); + + describe("arWithdrawRaiResponse rule tests", () => { + it("should return true for a valid package", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(true); + }); + it("should return false for a temporary extension package", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "Extend", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with a Withdrawn status", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.WITHDRAWN, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package that has an rai response", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawnDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with a status of Pending-Approval", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING_APPROVAL, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with raiWithdrawEnabled false", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: false, + }); + expect(arWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with a cms reviewer", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + }); + expect(arWithdrawRaiResponse.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package that is locked", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "New", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + raiReceivedDate: "2024-01-01T00:00:00.000Z", + raiWithdrawEnabled: true, + locked: true, + }); + expect(arWithdrawRaiResponse.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + }); + + describe("arWithdrawPackage rule tests", () => { + it("should return true for a valid package", () => { + const check = PackageCheck({ + ...TEST_1915C_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.PENDING, + }); + expect(arWithdrawPackage.check(check, TEST_STATE_SUBMITTER_USER)).toBe(true); + }); + it("should return false for a temporary extension package", () => { + const check = PackageCheck({ + ...TEST_1915C_ITEM?._source, + actionType: "Extend", + seatoolStatus: SEATOOL_STATUS.PENDING, + }); + expect(arWithdrawPackage.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with status Approved", () => { + const check = PackageCheck({ + ...TEST_1915C_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.APPROVED, + }); + expect(arWithdrawPackage.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with status Submitted", () => { + const check = PackageCheck({ + ...TEST_1915C_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.SUBMITTED, + }); + expect(arWithdrawPackage.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with a cms reviewer", () => { + const check = PackageCheck({ + ...TEST_1915C_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.PENDING, + }); + expect(arWithdrawPackage.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + }); + + describe("arUploadSubsequentDocuments rule tests", () => { + it("should return true for a valid package", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.PENDING, + }); + expect(arUploadSubsequentDocuments.check(check, TEST_STATE_SUBMITTER_USER)).toBe(true); + }); + it("should return false for a package with a cms reviewer", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.PENDING, + }); + expect(arUploadSubsequentDocuments.check(check, TEST_CMS_REVIEWER_USER)).toBe(false); + }); + it("should return false for a package that needs intake", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.PENDING, + initialIntakeNeeded: true, + }); + expect(arUploadSubsequentDocuments.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a temporary extension package", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + actionType: "Extend", + seatoolStatus: SEATOOL_STATUS.PENDING, + }); + expect(arUploadSubsequentDocuments.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with status Pending-RAI", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + }); + expect(arUploadSubsequentDocuments.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with a requested withdraw", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.PENDING, + raiRequestedDate: "2024-01-01T00:00:00.000Z", + }); + expect(arUploadSubsequentDocuments.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + it("should return false for a package with a non-Pending status", () => { + const check = PackageCheck({ + ...TEST_MED_SPA_ITEM?._source, + actionType: "New", + seatoolStatus: SEATOOL_STATUS.SUBMITTED, + }); + expect(arUploadSubsequentDocuments.check(check, TEST_STATE_SUBMITTER_USER)).toBe(false); + }); + }); +}); diff --git a/lib/packages/shared-utils/package-actions/rules.ts b/lib/packages/shared-utils/package-actions/rules.ts index d2df782cf9..08ae73d00e 100644 --- a/lib/packages/shared-utils/package-actions/rules.ts +++ b/lib/packages/shared-utils/package-actions/rules.ts @@ -7,7 +7,7 @@ import { } from "shared-types"; import { isStateUser, isCmsWriteUser, isCmsSuperUser } from "../user-helper"; -const arRespondToRai: ActionRule = { +export const arRespondToRai: ActionRule = { action: Action.RESPOND_TO_RAI, check: (checker, user) => !checker.isTempExtension && @@ -19,7 +19,7 @@ const arRespondToRai: ActionRule = { !checker.isLocked, }; -const arTempExtension: ActionRule = { +export const arTempExtension: ActionRule = { action: Action.TEMP_EXTENSION, check: (checker, user) => checker.hasStatus(SEATOOL_STATUS.APPROVED) && @@ -28,7 +28,7 @@ const arTempExtension: ActionRule = { isStateUser(user), }; -const arAmend: ActionRule = { +export const arAmend: ActionRule = { action: Action.AMEND_WAIVER, check: (checker, user) => checker.hasStatus(SEATOOL_STATUS.APPROVED) && @@ -37,7 +37,7 @@ const arAmend: ActionRule = { isStateUser(user), }; -const arEnableWithdrawRaiResponse: ActionRule = { +export const arEnableWithdrawRaiResponse: ActionRule = { action: Action.ENABLE_RAI_WITHDRAW, check: (checker, user) => { if (checker.authorityIs([Authority["CHIP_SPA"]])) { @@ -66,7 +66,7 @@ const arEnableWithdrawRaiResponse: ActionRule = { }, }; -const arDisableWithdrawRaiResponse: ActionRule = { +export const arDisableWithdrawRaiResponse: ActionRule = { action: Action.DISABLE_RAI_WITHDRAW, check: (checker, user) => !checker.isTempExtension && @@ -78,7 +78,7 @@ const arDisableWithdrawRaiResponse: ActionRule = { !checker.hasStatus([SEATOOL_STATUS.PENDING_CONCURRENCE, SEATOOL_STATUS.PENDING_APPROVAL]), }; -const arWithdrawRaiResponse: ActionRule = { +export const arWithdrawRaiResponse: ActionRule = { action: Action.WITHDRAW_RAI, check: (checker, user) => !checker.isTempExtension && @@ -92,7 +92,7 @@ const arWithdrawRaiResponse: ActionRule = { !checker.isLocked, }; -const arWithdrawPackage: ActionRule = { +export const arWithdrawPackage: ActionRule = { action: Action.WITHDRAW_PACKAGE, check: (checker, user) => !checker.isTempExtension && @@ -112,7 +112,7 @@ const arRemoveAppkChild: ActionRule = { check: (checker, user) => isStateUser(user) && !!checker.isAppkChild && false, }; -const arUploadSubsequentDocuments: ActionRule = { +export const arUploadSubsequentDocuments: ActionRule = { action: Action.UPLOAD_SUBSEQUENT_DOCUMENTS, check: (checker, user) => { if (isStateUser(user) === false) { diff --git a/lib/packages/shared-utils/package-check.test.ts b/lib/packages/shared-utils/package-check.test.ts index 3bbeb4509e..2f7d8d1559 100644 --- a/lib/packages/shared-utils/package-check.test.ts +++ b/lib/packages/shared-utils/package-check.test.ts @@ -1,48 +1,37 @@ import { describe, it, expect } from "vitest"; -import { testItemResult } from "./testData"; import { PackageCheck } from "."; import { ActionType, Authority, SEATOOL_STATUS } from "shared-types"; - -// Build Mock Package data: -// - make it basic, like a new submission -// - then override properties as needed -// ex: { ...baseNewSubmissionObj._source, raiWithdrawEnabled: true } +import { TEST_MED_SPA_ITEM, TEST_CHIP_SPA_ITEM, TEST_1915B_ITEM } from "mocks/data/items"; describe("PackageCheck", () => { describe("Plan Checks", () => { it("checks if isSpa", () => { let packageCheck = PackageCheck({ - ...testItemResult._source, - authority: Authority.MED_SPA, + ...TEST_MED_SPA_ITEM?._source, }); expect(packageCheck.isSpa).toBe(true); packageCheck = PackageCheck({ - ...testItemResult._source, - authority: Authority.CHIP_SPA, + ...TEST_CHIP_SPA_ITEM?._source, }); expect(packageCheck.isSpa).toBe(true); packageCheck = PackageCheck({ - ...testItemResult._source, - authority: Authority["1915b"], + ...TEST_1915B_ITEM?._source, }); expect(packageCheck.isSpa).toBe(false); }); it("checks if isWaiver", () => { let packageCheck = PackageCheck({ - ...testItemResult._source, - authority: Authority["1915b"], + ...TEST_1915B_ITEM?._source, }); expect(packageCheck.isWaiver).toBe(true); packageCheck = PackageCheck({ - ...testItemResult._source, - authority: Authority.CHIP_SPA, + ...TEST_CHIP_SPA_ITEM?._source, }); expect(packageCheck.isWaiver).toBe(false); }); it("checks against input", () => { const packageCheck = PackageCheck({ - ...testItemResult._source, - authority: Authority["1915b"], + ...TEST_1915B_ITEM._source, }); expect(packageCheck.authorityIs([Authority["1915b"]])).toBe(true); }); @@ -51,28 +40,26 @@ describe("PackageCheck", () => { describe("Status Checks", () => { it("checks if isInActivePendingStatus", () => { let packageCheck = PackageCheck({ - ...testItemResult._source, + ...TEST_MED_SPA_ITEM._source, seatoolStatus: SEATOOL_STATUS.PENDING, }); expect(packageCheck.isInActivePendingStatus).toBe(true); packageCheck = PackageCheck({ - ...testItemResult._source, + ...TEST_CHIP_SPA_ITEM._source, seatoolStatus: SEATOOL_STATUS.APPROVED, }); expect(packageCheck.isInActivePendingStatus).toBe(false); }); it("checks if isInSecondClock", () => { let packageCheck = PackageCheck({ - ...testItemResult._source, - authority: Authority.CHIP_SPA, // Chip Spas don't have 2nd clock + ...TEST_CHIP_SPA_ITEM._source, seatoolStatus: SEATOOL_STATUS.PENDING, raiRequestedDate: "exists", raiReceivedDate: "exists", }); expect(packageCheck.isInSecondClock).toBe(false); packageCheck = PackageCheck({ - ...testItemResult._source, - authority: Authority.MED_SPA, + ...TEST_MED_SPA_ITEM._source, seatoolStatus: SEATOOL_STATUS.PENDING, raiRequestedDate: "exists", raiReceivedDate: "exists", @@ -81,19 +68,19 @@ describe("PackageCheck", () => { }); it("checks if isNotWithdrawn", () => { let packageCheck = PackageCheck({ - ...testItemResult._source, + ...TEST_1915B_ITEM._source, seatoolStatus: SEATOOL_STATUS.WITHDRAWN, }); expect(packageCheck.isNotWithdrawn).toBe(false); packageCheck = PackageCheck({ - ...testItemResult._source, + ...TEST_MED_SPA_ITEM._source, seatoolStatus: SEATOOL_STATUS.APPROVED, }); expect(packageCheck.isNotWithdrawn).toBe(true); }); it("checks against input", () => { const packageCheck = PackageCheck({ - ...testItemResult._source, + ...TEST_CHIP_SPA_ITEM._source, seatoolStatus: SEATOOL_STATUS.WITHDRAWN, }); expect(packageCheck.hasStatus(SEATOOL_STATUS.PENDING_RAI)).toBe(false); @@ -104,23 +91,23 @@ describe("PackageCheck", () => { describe("RAI Checks", () => { it("checks if hasRequestedRai", () => {}); it("checks if hasLatestRai", () => { - let packageChecker = PackageCheck(testItemResult._source); + let packageChecker = PackageCheck(TEST_MED_SPA_ITEM._source); expect(packageChecker.hasLatestRai).toBe(false); packageChecker = PackageCheck({ - ...testItemResult._source, + ...TEST_MED_SPA_ITEM._source, raiRequestedDate: "yesterday, lol", }); expect(packageChecker.hasLatestRai).toBe(true); }); it("checks if hasRaiResponse", () => { let packageChecker = PackageCheck({ - ...testItemResult._source, + ...TEST_CHIP_SPA_ITEM._source, raiRequestedDate: "yesterday, lol", raiReceivedDate: "today, foo", }); expect(packageChecker.hasRaiResponse).toBe(true); packageChecker = PackageCheck({ - ...testItemResult._source, + ...TEST_CHIP_SPA_ITEM._source, raiRequestedDate: "yesterday, lol", raiReceivedDate: "today, foo", raiWithdrawnDate: "test", @@ -128,20 +115,23 @@ describe("PackageCheck", () => { expect(packageChecker.hasRaiResponse).toBe(false); }); it("checks if hasCompletedRai", () => { - let packageChecker = PackageCheck(testItemResult._source); + let packageChecker = PackageCheck(TEST_1915B_ITEM._source); expect(packageChecker.hasCompletedRai).toBe(false); packageChecker = PackageCheck({ - ...testItemResult._source, + ...TEST_1915B_ITEM._source, raiRequestedDate: "yesterday, lol", raiReceivedDate: "today, foo", }); expect(packageChecker.hasCompletedRai).toBe(true); }); it("checks if hasEnabledRaiWithdraw", () => { - let packageChecker = PackageCheck(testItemResult._source); + let packageChecker = PackageCheck({ + ...TEST_MED_SPA_ITEM._source, + raiWithdrawEnabled: false, + }); expect(packageChecker.hasEnabledRaiWithdraw).toBe(false); packageChecker = PackageCheck({ - ...testItemResult._source, + ...TEST_MED_SPA_ITEM._source, raiWithdrawEnabled: true, }); expect(packageChecker.hasEnabledRaiWithdraw).toBe(true); @@ -150,7 +140,7 @@ describe("PackageCheck", () => { describe("Action Type Checks", () => { it("checks against input", () => { const packageChecker = PackageCheck({ - ...testItemResult._source, + ...TEST_MED_SPA_ITEM._source, actionType: "Amend" as ActionType, }); diff --git a/lib/vitest.setup.ts b/lib/vitest.setup.ts index 61012f372a..494cde920d 100644 --- a/lib/vitest.setup.ts +++ b/lib/vitest.setup.ts @@ -1,8 +1,6 @@ import { afterAll, afterEach, beforeAll, beforeEach, vi } from "vitest"; import { - API_CONFIG, API_ENDPOINT, - AUTH_CONFIG, IDENTITY_POOL_ID, OPENSEARCH_DOMAIN, OPENSEARCH_INDEX_NAMESPACE, @@ -22,12 +20,7 @@ import { } from "mocks"; import { ConfigResourceTypes } from "kafkajs"; import { mockedServiceServer as mockedServer } from "mocks/server"; -import { Amplify } from "aws-amplify"; type CreateType = T & { default: T }; -Amplify.configure({ - API: API_CONFIG, - Auth: AUTH_CONFIG, -}); vi.mock("kafkajs", async (importOriginal) => ({ ...(await importOriginal()), diff --git a/mocks/consts.ts b/mocks/consts.ts index 6228e82b25..1032a7a4b9 100644 --- a/mocks/consts.ts +++ b/mocks/consts.ts @@ -37,8 +37,8 @@ export const AUTH_CONFIG = { userPoolWebClientId: USER_POOL_CLIENT_ID, oauth: { domain: USER_POOL_CLIENT_DOMAIN, - redirectSignIn: "http://localhost", - redirectSignOut: "http://localhost", + redirectSignIn: "http://localhost:5000/", + redirectSignOut: "http://localhost:5000/", scope: ["email", "openid"], responseType: "code", }, diff --git a/mocks/data/items.ts b/mocks/data/items.ts index 50b9f1e05a..78756ace8b 100644 --- a/mocks/data/items.ts +++ b/mocks/data/items.ts @@ -1,4 +1,4 @@ -import { SEATOOL_STATUS } from "shared-types"; +import { SEATOOL_STATUS, opensearch } from "shared-types"; import type { TestItemResult } from "../index.d"; import { ATTACHMENT_BUCKET_NAME } from "../consts"; @@ -17,7 +17,7 @@ export const HI_TEST_ITEM_ID = "HI-0000.R00.00"; export const CAPITATED_INITIAL_ITEM_ID = "SS-2234.R00.00"; export const CAPITATED_INITIAL_NEW_ITEM_ID = "SS-1235.R00.00"; export const CAPITATED_AMEND_ITEM_ID = "VA-2234.R11.01"; -export const WEIRD_ID = "VA"; +export const SIMPLE_ID = "VA"; export const CONTRACTING_INITIAL_ITEM_ID = "MD-007.R00.00"; export const CONTRACTING_AMEND_ITEM_ID = "MD-007.R00.01"; export const MISSING_CHANGELOG_ITEM_ID = "MD-008.R00.00"; @@ -26,6 +26,8 @@ export const INITIAL_RELEASE_APPK_ITEM_ID = "MD-010.R00.01"; export const EXISTING_ITEM_APPROVED_APPK_ITEM_ID = "MD-012.R00.01"; export const SUBMISSION_ERROR_ITEM_ID = "Throw Submission Error"; export const GET_ERROR_ITEM_ID = "Throw Get Item Error"; +export const WITHDRAW_RAI_ITEM_B = "VA-2234.R11.02"; +export const WITHDRAW_RAI_ITEM_C = "VA-2234.R11.03"; const items: Record = { [EXISTING_ITEM_ID]: { @@ -37,7 +39,7 @@ const items: Record = { actionType: "New", }, }, - [WEIRD_ID]: { + [SIMPLE_ID]: { _id: EXISTING_ITEM_ID, found: true, _source: { @@ -161,7 +163,7 @@ const items: Record = { _source: { id: EXISTING_ITEM_TEMPORARY_EXTENSION_ID, seatoolStatus: SEATOOL_STATUS.APPROVED, - actionType: "Amend", + actionType: "Extend", authority: "Medicaid SPA", changedDate: undefined, origin: "OneMAC", @@ -390,8 +392,10 @@ const items: Record = { appkChildren: [ { _source: { + authority: "1915(c)", changedDate: "2024-01-01T00:00:00Z", title: "Initial release", + seatoolStatus: SEATOOL_STATUS.PENDING, cmsStatus: "Pending", stateStatus: "Under Review", }, @@ -399,6 +403,72 @@ const items: Record = { ], }, }, + [WITHDRAW_RAI_ITEM_B]: { + _id: WITHDRAW_RAI_ITEM_B, + found: true, + _source: { + id: WITHDRAW_RAI_ITEM_B, + seatoolStatus: SEATOOL_STATUS.PENDING, + actionType: "respond-to-rai", + authority: "1915(b)", + state: "MD", + origin: "OneMAC", + changelog: [ + { + _id: `${WITHDRAW_RAI_ITEM_B}-001`, + _source: { + id: `${WITHDRAW_RAI_ITEM_B}-0001`, + event: "respond-to-rai", + packageId: WITHDRAW_RAI_ITEM_B, + }, + }, + ], + }, + }, + [WITHDRAW_RAI_ITEM_C]: { + _id: WITHDRAW_RAI_ITEM_C, + found: true, + _source: { + id: WITHDRAW_RAI_ITEM_C, + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + actionType: "respond-to-rai", + raiRequestedDate: "2024-01-01T00:00:00.000Z", + authority: "1915(c)", + state: "MD", + leadAnalystName: "lead test", + leadAnalystEmail: "Lead test email", + reviewTeam: [ + { + name: "Test", + email: "testemail", + }, + ], + origin: "OneMAC", + changelog: [ + { + _id: `${WITHDRAW_RAI_ITEM_C}-001`, + _source: { + id: `${WITHDRAW_RAI_ITEM_C}-0001`, + submitterName: "Testmctex", + submitterEmail: "fakeemail;", + event: "respond-to-rai", + packageId: WITHDRAW_RAI_ITEM_C, + }, + }, + + { + _id: `${WITHDRAW_RAI_ITEM_C}-002`, + _source: { + id: `${WITHDRAW_RAI_ITEM_C}-0002`, + submitterName: "Testmctex", + submitterEmail: "fakeemail;", + event: "withdraw-rai", + packageId: WITHDRAW_RAI_ITEM_C, + }, + }, + ], + }, + }, [EXISTING_ITEM_APPROVED_APPK_ITEM_ID]: { _id: EXISTING_ITEM_APPROVED_APPK_ITEM_ID, found: true, @@ -431,4 +501,18 @@ const items: Record = { }, }; +export const TEST_MED_SPA_ITEM = items[TEST_ITEM_ID] as opensearch.main.ItemResult; +export const TEST_CHIP_SPA_ITEM = items[WITHDRAWN_CHANGELOG_ITEM_ID] as opensearch.main.ItemResult; +export const TEST_1915B_ITEM = items[EXISTING_ITEM_APPROVED_NEW_ID] as opensearch.main.ItemResult; +export const TEST_1915C_ITEM = items[INITIAL_RELEASE_APPK_ITEM_ID] as opensearch.main.ItemResult; +export const TEST_ITEM_WITH_APPK = items[ + EXISTING_ITEM_APPROVED_APPK_ITEM_ID +] as opensearch.main.ItemResult; +export const TEST_ITEM_WITH_CHANGELOG = items[ + WITHDRAWN_CHANGELOG_ITEM_ID +] as opensearch.main.ItemResult; +export const TEST_TEMP_EXT_ITEM = items[ + EXISTING_ITEM_TEMPORARY_EXTENSION_ID +] as opensearch.main.ItemResult; + export default items; diff --git a/mocks/data/users/index.ts b/mocks/data/users/index.ts index 953555aca0..b51a1f2bd6 100644 --- a/mocks/data/users/index.ts +++ b/mocks/data/users/index.ts @@ -1,8 +1,9 @@ import type { TestUserData } from "../../index.d"; -import { reviewers } from "./cmsReviewer"; -import { helpDeskUsers } from "./helpDeskUsers"; -import { readOnlyUsers } from "./readOnlyCMSUsers"; -import { stateSubmitters } from "./stateSubmitters"; +import { reviewers, makoReviewer, superReviewer } from "./cmsReviewer"; +import { helpDeskUsers, helpDeskUser } from "./helpDeskUsers"; +import { readOnlyUsers, readOnlyUser } from "./readOnlyCMSUsers"; +import { stateSubmitters, makoStateSubmitter, coStateSubmitter } from "./stateSubmitters"; +import { convertUserAttributes } from "mocks/handlers/auth.utils"; export const noRoleUser: TestUserData = { UserAttributes: [ @@ -42,6 +43,13 @@ export const userResponses: TestUserData[] = [ // return an array of all usernames export default userResponses.map((response) => ({ username: response.Username })); +export const TEST_STATE_SUBMITTER_USER = convertUserAttributes(makoStateSubmitter); +export const TEST_CO_STATE_SUBMITTER_USER = convertUserAttributes(coStateSubmitter); +export const TEST_CMS_REVIEWER_USER = convertUserAttributes(makoReviewer); +export const TEST_HELP_DESK_USER = convertUserAttributes(helpDeskUser); +export const TEST_READ_ONLY_USER = convertUserAttributes(readOnlyUser); +export const TEST_SUPER_USER = convertUserAttributes(superReviewer); + export * from "./cmsReviewer"; export * from "./helpDeskUsers"; export * from "./mockStorage"; diff --git a/mocks/handlers/api/cpocs.ts b/mocks/handlers/api/cpocs.ts new file mode 100644 index 0000000000..80901c2a7d --- /dev/null +++ b/mocks/handlers/api/cpocs.ts @@ -0,0 +1,32 @@ +import { http, HttpResponse } from "msw"; +import { cpocsList } from "../../data/cpocs"; + +const defaultApiCpocHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getCpocs", + async () => + HttpResponse.json({ + took: 3, + timed_out: false, + _shards: { + total: 5, + successful: 5, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 654, + relation: "eq", + }, + max_score: 1, + hits: cpocsList, + }, + }), +); + +export const errorApiCpocHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getCpocs", + () => new HttpResponse(null, { status: 500 }), +); + +export const cpocHandlers = [defaultApiCpocHandler]; diff --git a/mocks/handlers/api/index.ts b/mocks/handlers/api/index.ts index abce82fd4e..95756a5557 100644 --- a/mocks/handlers/api/index.ts +++ b/mocks/handlers/api/index.ts @@ -1,9 +1,23 @@ +import { cpocHandlers } from "./cpocs"; import { itemHandlers } from "./items"; +import { packageActionHandlers } from "./packageActions"; +import { searchHandlers } from "./search"; import { submissionHandlers } from "./submissions"; import { typeHandlers } from "./types"; -export const apiHandlers = [...itemHandlers, ...submissionHandlers, ...typeHandlers]; +export const apiHandlers = [ + ...cpocHandlers, + ...itemHandlers, + ...packageActionHandlers, + ...searchHandlers, + ...submissionHandlers, + ...typeHandlers, +]; +export { errorApiCpocHandler } from "./cpocs"; +export { errorApiItemHandler, errorApiItemExistsHandler } from "./items"; +export { errorApiPackageActionsHandler } from "./packageActions"; +export { errorApiSearchHandler } from "./search"; +export { errorApiAttachmentUrlHandler } from "./submissions"; +export { errorApiSubTypesHandler, errorApiTypeHandler } from "./types"; export { mockCurrentAuthenticatedUser, mockUseGetUser, mockUserAttributes } from "./user"; - -export { errorSubTypesHandler, errorTypeHandler } from "./types"; diff --git a/mocks/handlers/api/items.ts b/mocks/handlers/api/items.ts index a7e83738d3..60eb6d2367 100644 --- a/mocks/handlers/api/items.ts +++ b/mocks/handlers/api/items.ts @@ -2,20 +2,28 @@ import { http, HttpResponse } from "msw"; import items, { GET_ERROR_ITEM_ID } from "../../data/items"; import type { GetItemBody } from "../../index.d"; -const defaultItemHandler = http.post(/\/item$/, async ({ request }) => { - const { id } = await request.json(); +const defaultApiItemHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/item", + async ({ request }) => { + const { id } = await request.json(); - if (id == GET_ERROR_ITEM_ID) { - return new HttpResponse("Internal server error", { status: 500 }); - } + if (id == GET_ERROR_ITEM_ID) { + return new HttpResponse("Internal server error", { status: 500 }); + } - const item = items[id] || null; + const item = items[id] || null; - return item ? HttpResponse.json(item) : new HttpResponse(null, { status: 404 }); -}); + return item ? HttpResponse.json(item) : new HttpResponse(null, { status: 404 }); + }, +); + +export const errorApiItemHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/item", + () => new HttpResponse(null, { status: 500 }), +); -const defaultItemExistsHandler = http.post( - /\/itemExists$/, +const defaultApiItemExistsHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/itemExists", async ({ request }) => { const { id } = await request.json(); if (id == GET_ERROR_ITEM_ID) { @@ -25,4 +33,9 @@ const defaultItemExistsHandler = http.post( }, ); -export const itemHandlers = [defaultItemHandler, defaultItemExistsHandler]; +export const errorApiItemExistsHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/itemExists", + () => new HttpResponse(null, { status: 500 }), +); + +export const itemHandlers = [defaultApiItemHandler, defaultApiItemExistsHandler]; diff --git a/mocks/handlers/api/packageActions.ts b/mocks/handlers/api/packageActions.ts new file mode 100644 index 0000000000..471e17081b --- /dev/null +++ b/mocks/handlers/api/packageActions.ts @@ -0,0 +1,47 @@ +import { http, HttpResponse, PathParams } from "msw"; +import { PackageActionsRequestBody, mockUseGetUser } from "mocks"; +import items from "mocks/data/items"; +import { opensearch, UserRoles } from "shared-types"; +import { getAvailableActions } from "shared-utils"; + +const defaultApiPackageActionsHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getPackageActions", + + async ({ request }) => { + const { id } = await request.json(); + + if (!id) { + return HttpResponse.json({ message: "Event body required" }, { status: 400 }); + } + + const item = items[id]; + if (!item?._source?.state) { + return HttpResponse.json({ message: "No record found for the given id" }, { status: 404 }); + } + + const currUser = mockUseGetUser()?.data?.user; + const userRoles = (currUser?.["custom:cms-roles"] as string) || ""; + const userStates = (currUser?.["custom:state"] as string) || ""; + if ( + !currUser || + !userRoles.includes(UserRoles.STATE_SUBMITTER) || + !userStates.includes(item._source.state) + ) { + return HttpResponse.json( + { message: "Not authorized to view resources from this state" }, + { status: 401 }, + ); + } + + return HttpResponse.json( + getAvailableActions(currUser, item._source as opensearch.main.Document) || [], + ); + }, +); + +export const errorApiPackageActionsHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getPackageActions", + () => new HttpResponse(null, { status: 500 }), +); + +export const packageActionHandlers = [defaultApiPackageActionsHandler]; diff --git a/mocks/handlers/api/search.ts b/mocks/handlers/api/search.ts new file mode 100644 index 0000000000..d1a51c0767 --- /dev/null +++ b/mocks/handlers/api/search.ts @@ -0,0 +1,39 @@ +import { http, HttpResponse } from "msw"; +import { cpocsList } from "../../data/cpocs"; + +const defaultApiSearchHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/search/:index", + ({ params }) => { + const { index } = params; + + if (index === "cpocs") { + return HttpResponse.json({ + took: 3, + timed_out: false, + _shards: { + total: 5, + successful: 5, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 654, + relation: "eq", + }, + max_score: 1, + hits: cpocsList, + }, + }); + } + + return new HttpResponse(null, { status: 200 }); + }, +); + +export const errorApiSearchHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/search/:index", + () => new HttpResponse("Internal server error", { status: 500 }), +); + +export const searchHandlers = [defaultApiSearchHandler]; diff --git a/mocks/handlers/api/submissions.ts b/mocks/handlers/api/submissions.ts index 10007c454f..446000141b 100644 --- a/mocks/handlers/api/submissions.ts +++ b/mocks/handlers/api/submissions.ts @@ -1,26 +1,43 @@ -import { http, HttpResponse } from "msw"; +import { http, HttpResponse, PathParams } from "msw"; import { SUBMISSION_ERROR_ITEM_ID } from "../../data/items"; +import { SubmitRequestBody, AttachmentUrlRequestBody } from "../../index.d"; +import { REGION } from "../../consts"; -export type SubmitRequestBody = { id: string }; - -const defaultUploadHandler = http.put( - /\/upload/, +const defaultApiUploadHandler = http.put( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/upload", async () => new HttpResponse(null, { status: 200 }), ); -const defaultUploadUrlHandler = http.post(/\/getUploadUrl/, () => - HttpResponse.json( - { - url: "/upload", - key: "test-key", - bucket: "test-bucket", - }, - { status: 200 }, - ), +const defaultApiUploadUrlHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getUploadUrl", + () => + HttpResponse.json( + { + url: "/upload", + key: "test-key", + bucket: "test-bucket", + }, + { status: 200 }, + ), +); + +const defaultApiAttachmentUrlHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getAttachmentUrl", + async ({ request }) => { + const { id, bucket, key, filename } = await request.json(); + return HttpResponse.json({ + url: `https://s3.${REGION}.amazonaws.com/${bucket}/${id}-${key}-${filename}`, + }); + }, +); + +export const errorApiAttachmentUrlHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getAttachmentUrl", + async () => new HttpResponse(null, { status: 500 }), ); -const defaultSubmitHandler = http.post( - /\/submit$/, +const defaultApiSubmitHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/submit", async ({ request }) => { const { id } = await request.json(); @@ -33,7 +50,8 @@ const defaultSubmitHandler = http.post( ); export const submissionHandlers = [ - defaultUploadHandler, - defaultUploadUrlHandler, - defaultSubmitHandler, + defaultApiUploadHandler, + defaultApiUploadUrlHandler, + defaultApiAttachmentUrlHandler, + defaultApiSubmitHandler, ]; diff --git a/mocks/handlers/api/types.ts b/mocks/handlers/api/types.ts index fda5255468..a9ccccd6da 100644 --- a/mocks/handlers/api/types.ts +++ b/mocks/handlers/api/types.ts @@ -4,29 +4,32 @@ import { types, subtypes } from "../../data/types"; type GetTypesBody = { authorityId: number }; type GetSubTypesBody = { authorityId: number; typeIds: number[] }; -const defaultTypeHandler = http.post(/\/getTypes$/, async ({ request }) => { - const { authorityId } = await request.json(); - - const hits = - types.filter( - (type) => - type?._source?.authorityId == authorityId && !type?._source?.name.match(/Do Not Use/), - ) || []; - - return HttpResponse.json({ - hits: { - hits, - }, - }); -}); - -export const errorTypeHandler = http.post( - /\/getTypes$/, +const defaultApiTypeHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getTypes", + async ({ request }) => { + const { authorityId } = await request.json(); + + const hits = + types.filter( + (type) => + type?._source?.authorityId == authorityId && !type?._source?.name.match(/Do Not Use/), + ) || []; + + return HttpResponse.json({ + hits: { + hits, + }, + }); + }, +); + +export const errorApiTypeHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getTypes", async () => new HttpResponse("Internal server error", { status: 500 }), ); -const defaultSubTypesHandler = http.post( - /\/getSubTypes$/, +const defaultApiSubTypesHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getSubTypes", async ({ request }) => { const { authorityId, typeIds } = await request.json(); @@ -46,9 +49,9 @@ const defaultSubTypesHandler = http.post( }, ); -export const errorSubTypesHandler = http.post( - /\/getSubTypes$/, +export const errorApiSubTypesHandler = http.post( + "https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests/getSubTypes", () => new HttpResponse("Internal server error", { status: 500 }), ); -export const typeHandlers = [defaultTypeHandler, defaultSubTypesHandler]; +export const typeHandlers = [defaultApiTypeHandler, defaultApiSubTypesHandler]; diff --git a/mocks/handlers/api/user.ts b/mocks/handlers/api/user.ts index 7047e4f3b1..48c07897fe 100644 --- a/mocks/handlers/api/user.ts +++ b/mocks/handlers/api/user.ts @@ -1,6 +1,6 @@ import { CognitoUserAttribute } from "amazon-cognito-identity-js"; import { isCmsUser } from "shared-utils"; -import { findUserByUsername, convertUserAttributes } from "../authUtils"; +import { findUserByUsername, convertUserAttributes } from "../auth.utils"; import type { TestUserData } from "../.."; // using `any` type here because the function that this is mocking uses any diff --git a/mocks/handlers/authUtils.ts b/mocks/handlers/auth.utils.ts similarity index 100% rename from mocks/handlers/authUtils.ts rename to mocks/handlers/auth.utils.ts diff --git a/mocks/handlers/aws/cognito.ts b/mocks/handlers/aws/cognito.ts index 969cafea99..a83d392dc5 100644 --- a/mocks/handlers/aws/cognito.ts +++ b/mocks/handlers/aws/cognito.ts @@ -15,7 +15,7 @@ import type { AdminGetUserRequestBody, TestUserData, } from "../../index.d"; -import { findUserByUsername } from "../authUtils"; +import { findUserByUsername } from "../auth.utils"; import { APIGatewayEventRequestContext } from "shared-types"; import { userResponses } from "../../data/users"; diff --git a/mocks/handlers/index.ts b/mocks/handlers/index.ts index fe792b7f7c..71de8b2158 100644 --- a/mocks/handlers/index.ts +++ b/mocks/handlers/index.ts @@ -36,7 +36,7 @@ export { setDefaultReviewer, setDefaultStateSubmitter, setMockUsername, -} from "./authUtils.js"; +} from "./auth.utils"; export * from "./api"; export * from "./aws"; diff --git a/mocks/handlers/opensearch/changelog.ts b/mocks/handlers/opensearch/changelog.ts index 456c3a71b3..99537d8496 100644 --- a/mocks/handlers/opensearch/changelog.ts +++ b/mocks/handlers/opensearch/changelog.ts @@ -2,9 +2,9 @@ import { http, HttpResponse, PathParams } from "msw"; import { GET_ERROR_ITEM_ID } from "../../data"; import items from "../../data/items"; import { SearchQueryBody, TestChangelogDocument, TestChangelogItemResult } from "../../index.d"; -import { getTermKeys, getTermValues, filterItemsByTerm } from "./util"; +import { getTermKeys, getTermValues, filterItemsByTerm } from "../search.utils"; -const defaultChangelogSearchHandler = http.post( +const defaultOSChangelogSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-changelog/_search", async ({ request }) => { const { query } = await request.json(); @@ -73,4 +73,4 @@ const defaultChangelogSearchHandler = http.post( }, ); -export const changelogSearchHandlers = [defaultChangelogSearchHandler]; +export const changelogSearchHandlers = [defaultOSChangelogSearchHandler]; diff --git a/mocks/handlers/opensearch/cpocs.ts b/mocks/handlers/opensearch/cpocs.ts index 8e603ca865..2a3c34c65c 100644 --- a/mocks/handlers/opensearch/cpocs.ts +++ b/mocks/handlers/opensearch/cpocs.ts @@ -1,7 +1,7 @@ import { http, HttpResponse } from "msw"; import { cpocsList } from "../../data/cpocs"; -const defaultCpocSearchHandler = http.post( +const defaultOSCpocSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-cpocs/_search", () => HttpResponse.json({ @@ -24,14 +24,14 @@ const defaultCpocSearchHandler = http.post( }), ); -export const emptyCpocSearchHandler = http.post( +export const emptyOSCpocSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-cpocs/_search", () => new HttpResponse(), ); -export const errorCpocSearchHandler = http.post( +export const errorOSCpocSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-cpocs/_search", () => new HttpResponse("Internal server error", { status: 500 }), ); -export const cpocSearchHandlers = [defaultCpocSearchHandler]; +export const cpocSearchHandlers = [defaultOSCpocSearchHandler]; diff --git a/mocks/handlers/opensearch/index.ts b/mocks/handlers/opensearch/index.ts index 5d0c78199b..844c985c14 100644 --- a/mocks/handlers/opensearch/index.ts +++ b/mocks/handlers/opensearch/index.ts @@ -16,13 +16,15 @@ export const opensearchHandlers = [ ...typeSearchHandlers, ]; -export { emptyCpocSearchHandler, errorCpocSearchHandler } from "./cpocs"; +export { emptyOSCpocSearchHandler, errorOSCpocSearchHandler } from "./cpocs"; export { errorCreateIndexHandler, errorUpdateFieldMappingHandler, errorBulkUpdateDataHandler, + rateLimitBulkUpdateDataHandler, errorDeleteIndexHandler, } from "./indices"; +export { errorOSMainMultiDocumentHandler } from "./main"; export { errorSecurityRolesMappingHandler } from "./security"; -export { errorSubtypeSearchHandler } from "./subtypes"; -export { errorTypeSearchHandler } from "./types"; +export { errorOSSubtypeSearchHandler } from "./subtypes"; +export { errorOSTypeSearchHandler } from "./types"; diff --git a/mocks/handlers/opensearch/indices.ts b/mocks/handlers/opensearch/indices.ts index 6a40734f8b..ea306e18de 100644 --- a/mocks/handlers/opensearch/indices.ts +++ b/mocks/handlers/opensearch/indices.ts @@ -30,6 +30,12 @@ const defaultBulkUpdateDataHandler = http.post( () => new HttpResponse(null, { status: 200 }), ); +export const rateLimitBulkUpdateDataHandler = http.post( + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/_bulk", + () => new HttpResponse("Rate limit exceeded", { status: 429 }), + { once: true }, +); + export const errorBulkUpdateDataHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/_bulk", () => new HttpResponse("Internal server error", { status: 500 }), diff --git a/mocks/handlers/opensearch/main.ts b/mocks/handlers/opensearch/main.ts index fdcd6efb7f..08e5295805 100644 --- a/mocks/handlers/opensearch/main.ts +++ b/mocks/handlers/opensearch/main.ts @@ -9,9 +9,9 @@ import { TestMainDocument, GetMultiItemBody, } from "../../index.d"; -import { getTermKeys, filterItemsByTerm, getTermValues } from "./util"; +import { getTermKeys, filterItemsByTerm, getTermValues } from "../search.utils"; -const defaultMainDocumentHandler = http.get( +const defaultOSMainDocumentHandler = http.get( `https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-main/_doc/:id`, async ({ params }) => { const { id } = params; @@ -29,7 +29,7 @@ const defaultMainDocumentHandler = http.get( }, ); -const defaultMainMultiDocumentHandler = http.post( +const defaultOSMainMultiDocumentHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-main/_mget", async ({ request }) => { const { ids } = await request.json(); @@ -42,7 +42,12 @@ const defaultMainMultiDocumentHandler = http.post( }, ); -const defaultMainSearchHandler = http.post( +export const errorOSMainMultiDocumentHandler = http.post( + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-main/_mget", + () => new HttpResponse("Internal server error", { status: 500 }), +); + +const defaultOSMainSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-main/_search", async ({ request }) => { const { query } = await request.json(); @@ -151,7 +156,7 @@ const defaultMainSearchHandler = http.post( ); export const mainSearchHandlers = [ - defaultMainDocumentHandler, - defaultMainMultiDocumentHandler, - defaultMainSearchHandler, + defaultOSMainDocumentHandler, + defaultOSMainMultiDocumentHandler, + defaultOSMainSearchHandler, ]; diff --git a/mocks/handlers/opensearch/subtypes.ts b/mocks/handlers/opensearch/subtypes.ts index 94151831d3..983cfbe896 100644 --- a/mocks/handlers/opensearch/subtypes.ts +++ b/mocks/handlers/opensearch/subtypes.ts @@ -1,9 +1,9 @@ import { http, HttpResponse, PathParams } from "msw"; import { subtypes } from "../../data/types"; import { SearchQueryBody } from "../../index.d"; -import { getFilterValueAsNumber, getFilterValueAsNumberArray } from "./util"; +import { getFilterValueAsNumber, getFilterValueAsNumberArray } from "../search.utils"; -const defaultSubtypeSearchHandler = http.post( +const defaultOSSubtypeSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-subtypes/_search", async ({ request }) => { const { query } = await request.json(); @@ -45,9 +45,9 @@ const defaultSubtypeSearchHandler = http.post( }, ); -export const errorSubtypeSearchHandler = http.post( +export const errorOSSubtypeSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-subtypes/_search", () => new HttpResponse("Internal server error", { status: 500 }), ); -export const subtypeSearchHandlers = [defaultSubtypeSearchHandler]; +export const subtypeSearchHandlers = [defaultOSSubtypeSearchHandler]; diff --git a/mocks/handlers/opensearch/types.ts b/mocks/handlers/opensearch/types.ts index 5c298ecd94..8928c2c4bc 100644 --- a/mocks/handlers/opensearch/types.ts +++ b/mocks/handlers/opensearch/types.ts @@ -1,9 +1,9 @@ import { http, HttpResponse, PathParams } from "msw"; import { types } from "../../data/types"; import { SearchQueryBody } from "../../index.d"; -import { getFilterValueAsNumber } from "./util"; +import { getFilterValueAsNumber } from "../search.utils"; -const defaultTypeSearchHandler = http.post( +const defaultOSTypeSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-types/_search", async ({ request }) => { const { query } = await request.json(); @@ -42,9 +42,9 @@ const defaultTypeSearchHandler = http.post( }, ); -export const errorTypeSearchHandler = http.post( +export const errorOSTypeSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-types/_search", () => new HttpResponse("Internal server error", { status: 500 }), ); -export const typeSearchHandlers = [defaultTypeSearchHandler]; +export const typeSearchHandlers = [defaultOSTypeSearchHandler]; diff --git a/mocks/handlers/opensearch/util.ts b/mocks/handlers/search.utils.ts similarity index 99% rename from mocks/handlers/opensearch/util.ts rename to mocks/handlers/search.utils.ts index 53ffd55d8e..608a7b2e13 100644 --- a/mocks/handlers/opensearch/util.ts +++ b/mocks/handlers/search.utils.ts @@ -1,4 +1,4 @@ -import { QueryContainer, TermQuery, TermsQuery, TestHit } from "../../index.d"; +import { QueryContainer, TermQuery, TermsQuery, TestHit } from ".."; export const getFilterValue = ( query: QueryContainer | QueryContainer[] | undefined, diff --git a/mocks/helpers/index.ts b/mocks/helpers/index.ts index f3ae14d1a0..a99e252ff3 100644 --- a/mocks/helpers/index.ts +++ b/mocks/helpers/index.ts @@ -1 +1 @@ -export * from "./kafka-test-helpers"; +export * from "./kafka.utils"; diff --git a/mocks/helpers/kafka-test-helpers.ts b/mocks/helpers/kafka.utils.ts similarity index 100% rename from mocks/helpers/kafka-test-helpers.ts rename to mocks/helpers/kafka.utils.ts diff --git a/mocks/index.d.ts b/mocks/index.d.ts index 4b8d79e68d..f4ab69605e 100644 --- a/mocks/index.d.ts +++ b/mocks/index.d.ts @@ -159,3 +159,16 @@ export type TestStepFunctionRequestBody = { }; export type TestCounty = [string, string, string]; + +export type SubmitRequestBody = { id: string }; + +export type AttachmentUrlRequestBody = { + id: string; + bucket: string; + key: string; + filename: string; +}; + +export type PackageActionsRequestBody = { + id: string; +}; diff --git a/react-app/.env.test b/react-app/.env.test new file mode 100644 index 0000000000..188f9ca37b --- /dev/null +++ b/react-app/.env.test @@ -0,0 +1,14 @@ +VITE_API_REGION="us-east-1" +VITE_API_URL=https://test-domain.execute-api.us-east-1.amazonaws.com/mocked-tests +VITE_NODE_ENV="development" +VITE_COGNITO_REGION=us-east-1 +VITE_COGNITO_IDENTITY_POOL_ID=us-east-1::test-identity-pool-id +VITE_COGNITO_USER_POOL_ID=us-east-1_userPool1 +VITE_COGNITO_USER_POOL_CLIENT_ID=userPoolWebClientId +VITE_COGNITO_USER_POOL_CLIENT_DOMAIN=mocked-tests-login-userPoolWebClientId.auth.us-east-1.amazoncognito.com +VITE_COGNITO_REDIRECT_SIGNIN="http://localhost:5000/" +VITE_COGNITO_REDIRECT_SIGNOUT="http://localhost:5000/" +VITE_IDM_HOME_URL=https://test.home.idm.cms.gov +VITE_GOOGLE_ANALYTICS_GTAG="" +VITE_GOOGLE_ANALYTICS_DISABLE="true" +VITE_LAUNCHDARKLY_CLIENT_ID="6638280397c1bc569aea5f3f" diff --git a/react-app/src/api/getAttachmentUrl.test.ts b/react-app/src/api/getAttachmentUrl.test.ts new file mode 100644 index 0000000000..7c387e5641 --- /dev/null +++ b/react-app/src/api/getAttachmentUrl.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { getAttachmentUrl } from "./getAttachmentUrl"; +import { + ATTACHMENT_BUCKET_NAME, + ATTACHMENT_BUCKET_REGION, + errorApiAttachmentUrlHandler, +} from "mocks"; +import { mockedApiServer as mockedServer } from "mocks/server"; + +describe("getAttachmentUrl tests", () => { + const id = "1234"; + const key = "test-key"; + const filename = "test-file.txt"; + + it("should return a url", async () => { + const url = await getAttachmentUrl(id, ATTACHMENT_BUCKET_NAME, key, filename); + expect(url).toEqual( + `https://s3.${ATTACHMENT_BUCKET_REGION}.amazonaws.com/${ATTACHMENT_BUCKET_NAME}/${id}-${key}-${filename}`, + ); + }); + + it("should throw an error if the response is 500", async () => { + mockedServer.use(errorApiAttachmentUrlHandler); + + await expect(() => + getAttachmentUrl(id, ATTACHMENT_BUCKET_NAME, key, filename), + ).rejects.toThrowError(); + }); +}); diff --git a/react-app/src/api/itemExists.test.ts b/react-app/src/api/itemExists.test.ts new file mode 100644 index 0000000000..0cc125abcd --- /dev/null +++ b/react-app/src/api/itemExists.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; +import { itemExists } from "./itemExists"; +import { + TEST_ITEM_ID, + NOT_FOUND_ITEM_ID, + NOT_EXISTING_ITEM_ID, + errorApiItemExistsHandler, +} from "mocks"; +import { mockedApiServer as mockedServer } from "mocks/server"; + +describe("itemExists test", () => { + it("should return true if the item exists", async () => { + const found = await itemExists(TEST_ITEM_ID); + expect(found).toBeTruthy(); + }); + + it("should return false if the item does not exist", async () => { + const found = await itemExists(NOT_EXISTING_ITEM_ID); + expect(found).toBeFalsy(); + }); + + it("should return false if the item is not found", async () => { + const found = await itemExists(NOT_FOUND_ITEM_ID); + expect(found).toBeFalsy(); + }); + + it("should return false if there is an error getting the item", async () => { + mockedServer.use(errorApiItemExistsHandler); + + const found = await itemExists(TEST_ITEM_ID); + expect(found).toBeFalsy(); + }); +}); diff --git a/react-app/src/api/itemExists.ts b/react-app/src/api/itemExists.ts index b3ebb72021..64091236c9 100644 --- a/react-app/src/api/itemExists.ts +++ b/react-app/src/api/itemExists.ts @@ -1,6 +1,11 @@ import { API } from "aws-amplify"; export const itemExists = async (id: string): Promise => { - const response = await API.post("os", "/itemExists", { body: { id } }); - return response.exists; + try { + const response = await API.post("os", "/itemExists", { body: { id } }); + return response.exists; + } catch (error) { + console.error("Error checking if item exists:", error); + return false; + } }; diff --git a/react-app/src/api/useGetCPOCs.test.ts b/react-app/src/api/useGetCPOCs.test.ts new file mode 100644 index 0000000000..60e5f9ff1c --- /dev/null +++ b/react-app/src/api/useGetCPOCs.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { fetchCpocData } from "./useGetCPOCs"; +import { errorApiCpocHandler } from "mocks"; +import { mockedApiServer as mockedServer } from "mocks/server"; +import { cpocsList } from "mocks/data/cpocs"; + +describe("useGetCPOCs test", () => { + describe("fetchCpocData tests", () => { + it("should return CPOCs", async () => { + const result = await fetchCpocData(); + expect(result).toEqual(cpocsList.map((cpoc) => cpoc?._source)); + }); + + it("should handle an error when fetching CPOCs", async () => { + mockedServer.use(errorApiCpocHandler); + + const result = await fetchCpocData(); + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/react-app/src/api/useGetCPOCs.ts b/react-app/src/api/useGetCPOCs.ts index c157a5f69b..35be30cdb9 100644 --- a/react-app/src/api/useGetCPOCs.ts +++ b/react-app/src/api/useGetCPOCs.ts @@ -4,10 +4,8 @@ import { ReactQueryApiError } from "shared-types"; import { cpocs } from "shared-types/opensearch"; export async function fetchCpocData() { - const endpoint = "/getCpocs"; - try { - const response = await API.post("os", endpoint, { body: {} }); + const response = await API.post("os", "/getCpocs", { body: {} }); const results = response.hits?.hits || []; return results.map((hit: cpocs.ItemResult) => hit._source); } catch (error) { diff --git a/react-app/src/api/useGetPackageActions.test.ts b/react-app/src/api/useGetPackageActions.test.ts new file mode 100644 index 0000000000..38fa246154 --- /dev/null +++ b/react-app/src/api/useGetPackageActions.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it, afterEach } from "vitest"; +import { getPackageActions } from "./useGetPackageActions"; +import { Action } from "shared-types"; +import { + errorApiPackageActionsHandler, + WITHDRAW_RAI_ITEM_C, + TEST_ITEM_ID, + NOT_FOUND_ITEM_ID, + NOT_EXISTING_ITEM_ID, + setMockUsername, + makoReviewer, + coStateSubmitter, + setDefaultStateSubmitter, +} from "mocks"; +import { mockedApiServer as mockedServer } from "mocks/server"; + +describe("getPackageActions test", () => { + afterEach(() => { + setDefaultStateSubmitter(); + }); + + it("should return actions for valid package", async () => { + const actions = await getPackageActions(WITHDRAW_RAI_ITEM_C); + expect(actions).toEqual([Action.RESPOND_TO_RAI, Action.WITHDRAW_PACKAGE]); + }); + + it("should return empty actions for package without actions", async () => { + const actions = await getPackageActions(TEST_ITEM_ID); + expect(actions).toEqual([]); + }); + + it("should return 400 if there is no package id", async () => { + await expect(() => getPackageActions(null)).rejects.toThrowError( + "Request failed with status code 400", + ); + }); + + it("should return 404 if the package is not found", async () => { + await expect(() => getPackageActions(NOT_FOUND_ITEM_ID)).rejects.toThrowError( + "Request failed with status code 404", + ); + }); + + it("should return 404 if the package does not exist", async () => { + await expect(() => getPackageActions(NOT_EXISTING_ITEM_ID)).rejects.toThrowError( + "Request failed with status code 404", + ); + }); + + it("should return 401 if the user is not a state submitter", async () => { + setMockUsername(makoReviewer); + + await expect(() => getPackageActions(WITHDRAW_RAI_ITEM_C)).rejects.toThrowError( + "Request failed with status code 401", + ); + }); + + it("should return 401 if the user is not a user for the state", async () => { + setMockUsername(coStateSubmitter); + + await expect(() => getPackageActions(WITHDRAW_RAI_ITEM_C)).rejects.toThrowError( + "Request failed with status code 401", + ); + }); + + it("should return 500 if there is a server error", async () => { + mockedServer.use(errorApiPackageActionsHandler); + + await expect(() => getPackageActions(WITHDRAW_RAI_ITEM_C)).rejects.toThrowError( + "Request failed with status code 500", + ); + }); +}); diff --git a/react-app/src/api/useGetPackageActions.ts b/react-app/src/api/useGetPackageActions.ts index 8349c9ef52..44a89bcf97 100644 --- a/react-app/src/api/useGetPackageActions.ts +++ b/react-app/src/api/useGetPackageActions.ts @@ -4,7 +4,7 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query"; type PackageActionsResponse = { actions: Action[]; }; -const getPackageActions = async (id: string): Promise => +export const getPackageActions = async (id: string): Promise => await API.post("os", "/getPackageActions", { body: { id } }); export const useGetPackageActions = ( diff --git a/react-app/src/api/useGetTypes.test.ts b/react-app/src/api/useGetTypes.test.ts index dacf6a638a..b4b321e039 100644 --- a/react-app/src/api/useGetTypes.test.ts +++ b/react-app/src/api/useGetTypes.test.ts @@ -14,7 +14,7 @@ import { chipTypes, chipSubtypes, } from "mocks/data/types"; -import { errorSubTypesHandler, errorTypeHandler } from "mocks"; +import { errorApiSubTypesHandler, errorApiTypeHandler } from "mocks"; import { mockedApiServer as mockedServer } from "mocks/server"; describe("fetchData", () => { @@ -35,7 +35,7 @@ describe("fetchData", () => { }); it("throws an error when fetch fails", async () => { - mockedServer.use(errorTypeHandler); + mockedServer.use(errorApiTypeHandler); await expect(fetchData({ authorityId: ERROR_AUTHORITY_ID })).rejects.toThrow( "Failed to fetch types", @@ -69,7 +69,7 @@ describe("fetchData", () => { }); it("throws an error when fetch fails", async () => { - mockedServer.use(errorSubTypesHandler); + mockedServer.use(errorApiSubTypesHandler); await expect(fetchData({ authorityId: ERROR_AUTHORITY_ID, typeIds: [] })).rejects.toThrow( "Failed to fetch subtypes", diff --git a/react-app/src/api/useSearch.test.ts b/react-app/src/api/useSearch.test.ts new file mode 100644 index 0000000000..c412d53ea7 --- /dev/null +++ b/react-app/src/api/useSearch.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { getOsData } from "./useSearch"; +import { cpocsList } from "mocks/data/cpocs"; + +describe("getOsData tests", () => { + it("should return cpocs", async () => { + const results = await getOsData({ + index: "cpocs", + sort: { + field: "lastName", + order: "asc", + }, + pagination: { + number: 0, + size: 20, + }, + filters: [], + }); + expect(results.hits.hits).toEqual(cpocsList); + }); +}); diff --git a/react-app/src/components/TimeoutModal/index.tsx b/react-app/src/components/TimeoutModal/index.tsx index 21359d7b99..6be317f055 100644 --- a/react-app/src/components/TimeoutModal/index.tsx +++ b/react-app/src/components/TimeoutModal/index.tsx @@ -62,7 +62,7 @@ export const TimeoutModal = () => { return ( - Session expiring soon + Session expiring soon Session expiring soon diff --git a/react-app/src/features/dashboard/index.tsx b/react-app/src/features/dashboard/index.tsx index 14bdd64e42..e7bc5c73cf 100644 --- a/react-app/src/features/dashboard/index.tsx +++ b/react-app/src/features/dashboard/index.tsx @@ -3,6 +3,7 @@ import { Plus as PlusIcon } from "lucide-react"; import { getUser, useGetUser } from "@/api"; import { WaiversList } from "./Lists/waivers"; import { SpasList } from "./Lists/spas"; +import { UserRoles } from "shared-types"; import { OsProvider, type OsTab, @@ -40,7 +41,14 @@ export const Dashboard = () => { const { data: userObj } = useGetUser(); const osData = useOsData(); - if (userObj === undefined) { + const isAbleToAccessDashboard = () => { + return ( + userObj.user["custom:cms-roles"] && + Object.values(UserRoles).some((role) => userObj.user["custom:cms-roles"].includes(role)) + ); + }; + + if (userObj === undefined || !isAbleToAccessDashboard()) { return ; } diff --git a/react-app/src/features/forms/post-submission/respond-to-rai/chip.test.tsx b/react-app/src/features/forms/post-submission/respond-to-rai/chip.test.tsx index edbea9d392..9e87104a4e 100644 --- a/react-app/src/features/forms/post-submission/respond-to-rai/chip.test.tsx +++ b/react-app/src/features/forms/post-submission/respond-to-rai/chip.test.tsx @@ -1,11 +1,11 @@ -import { screen } from "@testing-library/react"; -import { describe, expect, beforeAll, test } from "vitest"; import { RespondToRaiChip } from "@/features/forms/post-submission/respond-to-rai"; +import { formSchemas } from "@/formSchemas"; import { renderFormWithPackageSectionAsync } from "@/utils/test-helpers/renderForm"; import { skipCleanup } from "@/utils/test-helpers/skipCleanup"; import { uploadFiles } from "@/utils/test-helpers/uploadFiles"; -import { formSchemas } from "@/formSchemas"; +import { screen } from "@testing-library/react"; import { EXISTING_ITEM_PENDING_ID } from "mocks"; +import { beforeAll, describe, expect, test } from "vitest"; const upload = uploadFiles<(typeof formSchemas)["respond-to-rai-chip"]>(); @@ -15,12 +15,12 @@ describe("Respond To RAI CHIP", () => { await renderFormWithPackageSectionAsync(, EXISTING_ITEM_PENDING_ID); }); - test("REVISED AMENDED STATE PLAN LANGUAGE", async () => { + test("revised amended state plan language", async () => { const revisedAmendedStatePlanLanguageLabel = await upload("revisedAmendedStatePlanLanguage"); expect(revisedAmendedStatePlanLanguageLabel).not.toHaveClass("text-destructive"); }); - test("OFFICIAL RAI RESPONSE", async () => { + test("official RAI response", async () => { const officialRAIResponseLabel = await upload("officialRAIResponse"); expect(officialRAIResponseLabel).not.toHaveClass("text-destructive"); }); diff --git a/react-app/src/formSchemas/respond-to-rai.ts b/react-app/src/formSchemas/respond-to-rai.ts index 33da92d04c..db6ec2ccc3 100644 --- a/react-app/src/formSchemas/respond-to-rai.ts +++ b/react-app/src/formSchemas/respond-to-rai.ts @@ -8,11 +8,11 @@ export const formSchemaMedicaid = events["respond-to-rai"].baseSchema.extend({ export const formSchemaChip = events["respond-to-rai"].baseSchema.extend({ attachments: events["respond-to-rai"].chipSpaAttachments.extend({ revisedAmendedStatePlanLanguage: z.object({ - label: z.string().default("REVISED AMENDED STATE PLAN LANGUAGE"), + label: z.string().default("Revised Amended State Plan Language"), files: attachmentArraySchema(), }), officialRAIResponse: z.object({ - label: z.string().default("OFFICIAL RAI RESPONSE"), + label: z.string().default("Official RAI Response"), files: attachmentArraySchema(), }), }), diff --git a/vitest.config.ts b/vitest.config.ts index b1518d5c67..18ce87308e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ "build_run", ".cdk", "docs/**", + "**/vitest.setup.ts", "lib/libs/webforms/**", "lib/libs/email/mock-data/**", "react-app/src/features/webforms/**",