Skip to content

Commit

Permalink
refactor to use hash; add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gmrabian committed Oct 17, 2023
1 parent ee4c3d4 commit d319bfc
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 54 deletions.
21 changes: 0 additions & 21 deletions services/app-api/handlers/formTemplates/populateTemplatesTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
import { S3 } from "aws-sdk";
import * as path from "path";
import { logger } from "../../utils/logging";
import { AttributeValue, QueryInput } from "aws-sdk/clients/dynamodb";
import { createHash } from "crypto";

type S3ObjectRequired = SomeRequired<S3.Object, "Key" | "LastModified">;
Expand All @@ -34,26 +33,6 @@ type FormTemplateMetaData = {
hash: string;
};

/**
*
* @param reportType report type
* @param hash hash to look for
* @returns
*/
export function getTemplateVersionByHash(reportType: ReportType, hash: string) {
const queryParams: QueryInput = {
TableName: process.env.FORM_TEMPLATE_TABLE_NAME!,
IndexName: "HashIndex",
KeyConditionExpression: "reportType = :reportType AND md5Hash = :md5Hash",
Limit: 1,
ExpressionAttributeValues: {
":md5Hash": hash as AttributeValue,
":reportType": reportType as unknown as AttributeValue,
},
};
return dynamodbLib.query(queryParams);
}

/**
* Retrieve template data from S3
*
Expand Down
5 changes: 4 additions & 1 deletion services/app-api/handlers/reports/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,14 @@ export const createReport = handler(async (event, _context) => {

let formTemplate, formTemplateVersion;

const isProgramPCCM =
unvalidatedMetadata?.programIsPCCM?.[0]?.value === "Yes";

try {
({ formTemplate, formTemplateVersion } = await getOrCreateFormTemplate(
reportBucket,
reportType,
unvalidatedMetadata
isProgramPCCM
));
} catch (err) {
logger.error(err, "Error getting or creating template");
Expand Down
93 changes: 73 additions & 20 deletions services/app-api/utils/formTemplates/formTemplates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
compileValidationJsonFromRoutes,
flattenReportRoutesArray,
formTemplateForReportType,
generatePCCMTemplate,
getOrCreateFormTemplate,
getValidationFromFormTemplate,
isFieldElement,
Expand All @@ -15,6 +16,11 @@ import { mockDocumentClient, mockReportJson } from "../testing/setupJest";
import s3Lib from "../s3/s3-lib";
import dynamodbLib from "../dynamo/dynamodb-lib";

const programIsPCCM = true;
const programIsNotPCCM = false;

global.structuredClone = (val: any) => JSON.parse(JSON.stringify(val));

const currentMLRFormHash = createHash("md5")
.update(JSON.stringify(mlr))
.digest("hex");
Expand All @@ -23,19 +29,30 @@ const currentMCPARFormHash = createHash("md5")
.update(JSON.stringify(mcpar))
.digest("hex");

const pccmTemplate = generatePCCMTemplate(mcpar);
const currentPCCMFormHash = createHash("md5")
.update(JSON.stringify(pccmTemplate))
.digest("hex");

describe("Test getOrCreateFormTemplate MCPAR", () => {
beforeEach(() => {
jest.restoreAllMocks();
});
it("should create a new form template if none exist", async () => {
// mocked once for search by hash
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [],
});
// mocked again for search for latest report
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [],
});
const dynamoPutSpy = jest.spyOn(dynamodbLib, "put");
const s3PutSpy = jest.spyOn(s3Lib, "put");
const result = await getOrCreateFormTemplate(
"local-mcpar-reports",
ReportType.MCPAR
ReportType.MCPAR,
programIsNotPCCM
);
expect(dynamoPutSpy).toHaveBeenCalled();
expect(s3PutSpy).toHaveBeenCalled();
Expand All @@ -47,7 +64,34 @@ describe("Test getOrCreateFormTemplate MCPAR", () => {
expect(result.formTemplateVersion?.md5Hash).toEqual(currentMCPARFormHash);
});

it("should create a new form template for PCCM if none exist", async () => {
// mocked once for search by hash
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [],
});
// mocked again for search for latest report
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [],
});
const dynamoPutSpy = jest.spyOn(dynamodbLib, "put");
const s3PutSpy = jest.spyOn(s3Lib, "put");
const result = await getOrCreateFormTemplate(
"local-mcpar-reports",
ReportType.MCPAR,
programIsPCCM
);
expect(dynamoPutSpy).toHaveBeenCalled();
expect(s3PutSpy).toHaveBeenCalled();
expect(result.formTemplate).toEqual({
...pccmTemplate,
validationJson: getValidationFromFormTemplate(pccmTemplate as ReportJson),
});
expect(result.formTemplateVersion?.versionNumber).toEqual(1);
expect(result.formTemplateVersion?.md5Hash).toEqual(currentPCCMFormHash);
});

it("should return the right form and formTemplateVersion if it matches the most recent form", async () => {
// mocked once for search by hash
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [
{
Expand All @@ -56,19 +100,14 @@ describe("Test getOrCreateFormTemplate MCPAR", () => {
md5Hash: currentMCPARFormHash,
versionNumber: 3,
},
{
formTemplateId: "foo",
id: "mockReportJson",
md5Hash: currentMCPARFormHash + "111",
versionNumber: 2,
},
],
});
const dynamoPutSpy = jest.spyOn(dynamodbLib, "put");
const s3PutSpy = jest.spyOn(s3Lib, "put");
const result = await getOrCreateFormTemplate(
"local-mcpar-reports",
ReportType.MCPAR
ReportType.MCPAR,
programIsNotPCCM
);
expect(dynamoPutSpy).not.toHaveBeenCalled();
expect(s3PutSpy).not.toHaveBeenCalled();
Expand All @@ -77,6 +116,11 @@ describe("Test getOrCreateFormTemplate MCPAR", () => {
});

it("should create a new form if it doesn't match the most recent form", async () => {
// mocked once for search by hash
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [],
});
// mocked again for search for latest report
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [
{
Expand All @@ -97,7 +141,8 @@ describe("Test getOrCreateFormTemplate MCPAR", () => {
const s3PutSpy = jest.spyOn(s3Lib, "put");
const result = await getOrCreateFormTemplate(
"local-mcpar-reports",
ReportType.MCPAR
ReportType.MCPAR,
programIsNotPCCM
);
expect(dynamoPutSpy).toHaveBeenCalled();
expect(s3PutSpy).toHaveBeenCalled();
Expand All @@ -110,14 +155,20 @@ describe("Test getOrCreateFormTemplate MLR", () => {
jest.restoreAllMocks();
});
it("should create a new form template if none exist", async () => {
// mocked once for search by hash
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [],
});
// mocked again for search for latest report
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [],
});
const dynamoPutSpy = jest.spyOn(dynamodbLib, "put");
const s3PutSpy = jest.spyOn(s3Lib, "put");
const result = await getOrCreateFormTemplate(
"local-mlr-reports",
ReportType.MLR
ReportType.MLR,
programIsNotPCCM
);
expect(dynamoPutSpy).toHaveBeenCalled();
expect(s3PutSpy).toHaveBeenCalled();
Expand All @@ -130,6 +181,7 @@ describe("Test getOrCreateFormTemplate MLR", () => {
});

it("should return the right form and formTemplateVersion if it matches the most recent form", async () => {
// mocked once for search by hash
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [
{
Expand All @@ -138,19 +190,14 @@ describe("Test getOrCreateFormTemplate MLR", () => {
md5Hash: currentMLRFormHash,
versionNumber: 3,
},
{
formTemplateId: "foo",
id: "mockReportJson",
md5Hash: currentMLRFormHash + "111",
versionNumber: 2,
},
],
});
const dynamoPutSpy = jest.spyOn(dynamodbLib, "put");
const s3PutSpy = jest.spyOn(s3Lib, "put");
const result = await getOrCreateFormTemplate(
"local-mlr-reports",
ReportType.MLR
ReportType.MLR,
programIsNotPCCM
);
expect(dynamoPutSpy).not.toHaveBeenCalled();
expect(s3PutSpy).not.toHaveBeenCalled();
Expand All @@ -159,18 +206,23 @@ describe("Test getOrCreateFormTemplate MLR", () => {
});

it("should create a new form if it doesn't match the most recent form", async () => {
// mocked once for search by hash
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [],
});
// mocked again for search for latest report
mockDocumentClient.query.promise.mockReturnValueOnce({
Items: [
{
formTemplateId: "foo",
id: "mockReportJson",
md5Hash: currentMLRFormHash + "111111",
md5Hash: currentMCPARFormHash + "111111",
versionNumber: 3,
},
{
formTemplateId: "foo",
id: "mockReportJson",
md5Hash: currentMLRFormHash + "111",
md5Hash: currentMCPARFormHash + "111",
versionNumber: 2,
},
],
Expand All @@ -179,7 +231,8 @@ describe("Test getOrCreateFormTemplate MLR", () => {
const s3PutSpy = jest.spyOn(s3Lib, "put");
const result = await getOrCreateFormTemplate(
"local-mlr-reports",
ReportType.MLR
ReportType.MLR,
programIsNotPCCM
);
expect(dynamoPutSpy).toHaveBeenCalled();
expect(s3PutSpy).toHaveBeenCalled();
Expand Down
45 changes: 33 additions & 12 deletions services/app-api/utils/formTemplates/formTemplates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
FormField,
FormLayoutElement,
FormTemplate,
MCPARReportMetadata,
ModalOverlayReportPageShape,
ReportJson,
ReportRoute,
Expand All @@ -35,6 +34,24 @@ export async function getNewestTemplateVersion(reportType: ReportType) {
return result.Items?.[0];
}

export async function getTemplateVersionByHash(
reportType: ReportType,
hash: string
) {
const queryParams: QueryInput = {
TableName: process.env.FORM_TEMPLATE_TABLE_NAME!,
IndexName: "HashIndex",
KeyConditionExpression: "reportType = :reportType AND md5Hash = :md5Hash",
Limit: 1,
ExpressionAttributeValues: {
":md5Hash": hash as AttributeValue,
":reportType": reportType as unknown as AttributeValue,
},
};
const result = await dynamodbLib.query(queryParams);
return result.Items?.[0];
}

export const formTemplateForReportType = (reportType: ReportType) => {
switch (reportType) {
case ReportType.MCPAR:
Expand All @@ -56,11 +73,10 @@ export const formTemplateForReportType = (reportType: ReportType) => {
export async function getOrCreateFormTemplate(
reportBucket: string,
reportType: ReportType,
metadata: MCPARReportMetadata
isProgramPCCM: boolean
) {
let currentFormTemplate = formTemplateForReportType(reportType);
// if program is PCCM generate shortened template
if (metadata?.programIsPCCM?.[0]?.value === "Yes") {
if (isProgramPCCM) {
currentFormTemplate = generatePCCMTemplate(currentFormTemplate);
}
const stringifiedTemplate = JSON.stringify(currentFormTemplate);
Expand All @@ -69,16 +85,18 @@ export async function getOrCreateFormTemplate(
.update(stringifiedTemplate)
.digest("hex");

const mostRecentTemplateVersion = await getNewestTemplateVersion(reportType);
const mostRecentTemplateVersionHash = mostRecentTemplateVersion?.md5Hash;
const matchingTemplateMetadata = await getTemplateVersionByHash(
reportType,
currentTemplateHash
);

if (currentTemplateHash === mostRecentTemplateVersionHash) {
if (matchingTemplateMetadata) {
return {
formTemplate: await getTemplate(
reportBucket,
getFormTemplateKey(mostRecentTemplateVersion?.id)
getFormTemplateKey(matchingTemplateMetadata?.id)
),
formTemplateVersion: mostRecentTemplateVersion,
formTemplateVersion: matchingTemplateMetadata,
};
} else {
const newFormTemplateId = KSUID.randomSync().string;
Expand All @@ -98,10 +116,12 @@ export async function getOrCreateFormTemplate(
throw err;
}

const newestTemplateMetadata = await getNewestTemplateVersion(reportType);

// If we didn't find any form templates, start version at 1.
const newFormTemplateVersionItem: FormTemplate = {
versionNumber: mostRecentTemplateVersion?.versionNumber
? (mostRecentTemplateVersion.versionNumber += 1)
versionNumber: newestTemplateMetadata?.versionNumber
? (newestTemplateMetadata.versionNumber += 1)
: 1,
md5Hash: currentTemplateHash,
id: newFormTemplateId,
Expand Down Expand Up @@ -265,7 +285,8 @@ const routesToIncludeInPCCM = {

const entitiesToIncludeInPCCM = ["plans", "sanctions"];

export const generatePCCMTemplate = (reportTemplate: any) => {
export const generatePCCMTemplate = (originalReportTemplate: any) => {
const reportTemplate = structuredClone(originalReportTemplate);
// remove top level sections not in include list
reportTemplate.routes = reportTemplate.routes.filter(
(route: ReportRoute) => !!routesToIncludeInPCCM[route.name]
Expand Down

0 comments on commit d319bfc

Please sign in to comment.