-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(lambda): Create split SPAs 2 (#1085)
* update id broken * ad dlog * log * log * adjust logic * hm * why * log getpackage * logs * revert? * log os * log * log * client.get stuck * fix * fix * log error * what * revert * revert * wip * validate id * zod error * await * fix query * fix query again * fix query * try keyword * try * query * test * log * last index * logic * test * cleanup * edit names and sinkmainprocessor * weird * log * fix * huh * log * bug * admin change schema * missing in schema * idToBeUpdated * add changelog type * zod typo? * facepalm * admin package activity * log changelog pushing, add success response * debug changelog * log docs * change split spa changelog logic * add tests wip * clean up and fix error bubbling * reference baseschema id shape * fix import errors and tests wip * test after refactor bug * test fix * fix * rm logs and update comments * change body parsing, update timestamps, mod admin schema * fix timestamp * hm * was it this line * test change * revert * log not incrementing * log fix * log hits * m not showing in hits * look for m * query size? * syntax fix * rm logs and test admin schema change again * revert * import error * import again * revert * remove * topic name not defined * rm unnecessary packageId check and wip tests * reduce query size * reduce query size? * change order of pushing? * remove query size * consistent date time * update test fix error rejection * add regexp to query type for split spas * modify mocked os search func to accomodate for split spas * sorry adding split spas into mock items * put query size back * tears. * update test to use requestContext * revert packageExists * correct timestamp and add mockEvent for upload sub docs * remove example json field * mod example json
- Loading branch information
Showing
15 changed files
with
353 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { search } from "libs/opensearch-lib"; | ||
import { getDomainAndNamespace } from "libs/utils"; | ||
import { cpocs } from "lib/packages/shared-types/opensearch"; | ||
|
||
export const getNextSplitSPAId = async (spaId: string) => { | ||
const { domain, index } = getDomainAndNamespace("main"); | ||
const query = { | ||
size: 50, | ||
query: { | ||
regexp: { | ||
"id.keyword": `${spaId}-[A-Z]`, | ||
}, | ||
}, | ||
}; | ||
// Get existing split SPAs for this package id | ||
const { hits } = await search(domain, index, query); | ||
// Extract suffixes from existing split SPA IDs | ||
// If there are no split SPAs yet, start at the ASCII character before "A" ("@") | ||
// Convert to ASCII char codes to get latest suffix | ||
const latestSuffixCharCode = hits.hits.reduce((maxCharCode: number, hit: cpocs.ItemResult) => { | ||
const suffix = hit._source.id.toString().split("-").at(-1) ?? "@"; | ||
return Math.max(maxCharCode, suffix.charCodeAt(0)); | ||
}, "@".charCodeAt(0)); | ||
|
||
// Increment letter but not past "Z" | ||
// "A-Z" is 65-90 in ASCII | ||
if (latestSuffixCharCode >= 90) { | ||
throw new Error("This package can't be further split."); | ||
} | ||
const nextSuffix = String.fromCharCode(latestSuffixCharCode + 1); | ||
|
||
return `${spaId}-${nextSuffix}`; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { describe, it, expect, vi, beforeEach } from "vitest"; | ||
import { handler } from "./submitSplitSPA"; | ||
import { APIGatewayEvent } from "node_modules/shared-types"; | ||
import { | ||
getRequestContext, | ||
TEST_CHIP_SPA_ITEM, | ||
TEST_MED_SPA_ITEM, | ||
TEST_SPA_ITEM_TO_SPLIT, | ||
} from "mocks"; | ||
|
||
describe("handler", () => { | ||
beforeEach(() => { | ||
vi.clearAllMocks(); | ||
process.env.topicName = "test-topic"; | ||
}); | ||
|
||
it("should return 400 if event body is missing", async () => { | ||
const event = {} as APIGatewayEvent; | ||
|
||
const result = await handler(event); | ||
|
||
expect(result?.statusCode).toEqual(400); | ||
}); | ||
|
||
it("should return 404 if package ID is not found", async () => { | ||
const invalidPackage = { | ||
body: JSON.stringify({ packageId: "MD-25-9999" }), | ||
} as unknown as APIGatewayEvent; | ||
|
||
const result = await handler(invalidPackage); | ||
|
||
expect(result?.statusCode).toEqual(404); | ||
}); | ||
|
||
it("should throw an error if not Medicaid SPA", async () => { | ||
const chipSPAPackage = { | ||
body: JSON.stringify({ packageId: TEST_CHIP_SPA_ITEM._id }), | ||
requestContext: getRequestContext(), | ||
} as APIGatewayEvent; | ||
|
||
const result = await handler(chipSPAPackage); | ||
|
||
expect(result.body).toEqual(JSON.stringify({ message: "Record must be a Medicaid SPA" })); | ||
}); | ||
|
||
it("should return 400 if package ID not provided", async () => { | ||
const invalidPackage = { | ||
body: JSON.stringify({}), | ||
} as unknown as APIGatewayEvent; | ||
|
||
const result = await handler(invalidPackage); | ||
|
||
expect(result?.statusCode).toEqual(400); | ||
}); | ||
|
||
it("should fail to split a package with no topic name", async () => { | ||
delete process.env.topicName; | ||
|
||
const noActionevent = { | ||
body: JSON.stringify({ | ||
packageId: TEST_MED_SPA_ITEM._id, | ||
}), | ||
} as APIGatewayEvent; | ||
|
||
await expect(handler(noActionevent)).rejects.toThrow("Topic name is not defined"); | ||
}); | ||
|
||
it("should create a split SPA", async () => { | ||
const medSPAPackage = { | ||
body: JSON.stringify({ packageId: TEST_MED_SPA_ITEM._id }), | ||
} as unknown as APIGatewayEvent; | ||
|
||
const result = await handler(medSPAPackage); | ||
expect(result?.statusCode).toEqual(200); | ||
}); | ||
|
||
it("should fail if unable to get next split SPA suffix", async () => { | ||
const medSPAPackage = { | ||
body: JSON.stringify({ packageId: TEST_SPA_ITEM_TO_SPLIT }), | ||
} as unknown as APIGatewayEvent; | ||
|
||
await expect(handler(medSPAPackage)).rejects.toThrow("This package can't be further split."); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { response } from "libs/handler-lib"; | ||
import { APIGatewayEvent } from "aws-lambda"; | ||
import { getPackage } from "libs/api/package"; | ||
import { produceMessage } from "libs/api/kafka"; | ||
import { ItemResult } from "shared-types/opensearch/main"; | ||
import { events } from "shared-types/events"; | ||
import { getNextSplitSPAId } from "./getNextSplitSPAId"; | ||
import { z } from "zod"; | ||
|
||
/* | ||
EXAMPLE EVENT JSON: | ||
{ | ||
"body": { | ||
"packageId": "MD-25-9999", | ||
} | ||
} | ||
*/ | ||
|
||
const sendSubmitSplitSPAMessage = async (currentPackage: ItemResult) => { | ||
const topicName = process.env.topicName as string; | ||
if (!topicName) { | ||
throw new Error("Topic name is not defined"); | ||
} | ||
const newId = await getNextSplitSPAId(currentPackage._id); | ||
if (!newId) { | ||
throw new Error("Error getting next Split SPA Id"); | ||
} | ||
|
||
// ID and changeMade are excluded; the rest of the object has to be spread into the new package | ||
const { | ||
id: _id, | ||
changeMade: _changeMade, | ||
origin: _origin, | ||
...remainingFields | ||
} = currentPackage._source; | ||
|
||
await produceMessage( | ||
topicName, | ||
newId, | ||
JSON.stringify({ | ||
id: newId, | ||
idToBeUpdated: currentPackage._id, | ||
...remainingFields, | ||
makoChangedDate: Date.now(), | ||
changedDate: Date.now(), | ||
origin: "OneMAC", | ||
changeMade: "OneMAC Admin has added a package to OneMAC.", | ||
changeReason: "Per request from CMS, this package was added to OneMAC.", | ||
mockEvent: "new-medicaid-submission", | ||
isAdminChange: true, | ||
adminChangeType: "split-spa", | ||
}), | ||
); | ||
|
||
return response({ | ||
statusCode: 200, | ||
body: { message: `New Medicaid Split SPA ${newId} has been created.` }, | ||
}); | ||
}; | ||
|
||
const splitSPAEventBodySchema = z.object({ | ||
packageId: events["new-medicaid-submission"].baseSchema.shape.id, | ||
}); | ||
|
||
export const handler = async (event: APIGatewayEvent) => { | ||
if (!event.body) { | ||
return response({ | ||
statusCode: 400, | ||
body: { message: "Event body required" }, | ||
}); | ||
} | ||
try { | ||
const body = typeof event.body === "string" ? JSON.parse(event.body) : event.body; | ||
const { packageId } = splitSPAEventBodySchema.parse(body); | ||
|
||
const currentPackage = await getPackage(packageId); | ||
if (!currentPackage || currentPackage.found == false) { | ||
return response({ | ||
statusCode: 404, | ||
body: { message: "No record found for the given id" }, | ||
}); | ||
} | ||
|
||
if (currentPackage._source.authority !== "Medicaid SPA") { | ||
return response({ | ||
statusCode: 400, | ||
body: { message: "Record must be a Medicaid SPA" }, | ||
}); | ||
} | ||
|
||
return sendSubmitSplitSPAMessage(currentPackage); | ||
} catch (err) { | ||
console.error("Error has occured modifying package:", err); | ||
if (err instanceof z.ZodError) { | ||
return response({ | ||
statusCode: 400, | ||
body: { message: err.errors }, | ||
}); | ||
} | ||
return response({ | ||
statusCode: 500, | ||
body: { message: err.message || "Internal Server Error" }, | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.