Skip to content

Commit

Permalink
Merge branch 'timeout-faq' of github.com:Enterprise-CMCS/macpro-mako …
Browse files Browse the repository at this point in the history
…into timeout-faq
  • Loading branch information
tbolt committed Jan 24, 2025
2 parents 082ee5e + 5959c18 commit 77ca522
Show file tree
Hide file tree
Showing 35 changed files with 1,550 additions and 158 deletions.
Binary file modified bun.lockb
Binary file not shown.
6 changes: 6 additions & 0 deletions lib/config/deployment-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ describe("DeploymentConfig", () => {
domainCertificateArn: "domainCertificateArn",
domainName: "domainName",
emailAddressLookupSecretName: "emailAddressLookupSecretName", // pragma: allowlist secret
notificationSecretName: "notificationSecretName", // pragma: allowlist secret
notificationSecretArn: "notificationSecretArn", // pragma: allowlist secret
googleAnalyticsDisable: "true",
googleAnalyticsGTag: "googleAnalyticsGTag",
idmAuthzApiEndpoint: "idmAuthzApiEndpoint",
Expand Down Expand Up @@ -81,6 +83,8 @@ describe("DeploymentConfig", () => {
domainCertificateArn: "domainCertificateArn",
domainName: "stage-domainName", // Overridden by stage secret
emailAddressLookupSecretName: "emailAddressLookupSecretName", // pragma: allowlist secret
notificationSecretName: "notificationSecretName", // pragma: allowlist secret
notificationSecretArn: "notificationSecretArn", // pragma: allowlist secret
googleAnalyticsDisable: false, // Converted to boolean and overridden by stage secret
googleAnalyticsGTag: "googleAnalyticsGTag",
idmAuthzApiEndpoint: "idmAuthzApiEndpoint",
Expand Down Expand Up @@ -145,6 +149,8 @@ describe("DeploymentConfig", () => {
domainCertificateArn: "domainCertificateArn",
domainName: "domainName",
emailAddressLookupSecretName: "emailAddressLookupSecretName", // pragma: allowlist secret
notificationSecretName: "notificationSecretName", // pragma: allowlist secret
notificationSecretArn: "notificationSecretArn", // pragma: allowlist secret
googleAnalyticsDisable: true,
googleAnalyticsGTag: "googleAnalyticsGTag",
idmAuthzApiEndpoint: "idmAuthzApiEndpoint",
Expand Down
4 changes: 4 additions & 0 deletions lib/config/deployment-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type InjectedConfigProperties = {
domainCertificateArn: string;
domainName: string;
emailAddressLookupSecretName: string;
notificationSecretName: string;
notificationSecretArn: string;
googleAnalyticsDisable: boolean;
googleAnalyticsGTag: string;
iamPath: string;
Expand Down Expand Up @@ -117,6 +119,8 @@ export class DeploymentConfig {
typeof config.domainCertificateArn == "string" &&
typeof config.domainName === "string" &&
typeof config.emailAddressLookupSecretName === "string" && // pragma: allowlist secret
typeof config.notificationSecretName === "string" && // pragma: allowlist secret
typeof config.notificationSecretArn === "string" && // pragma: allowlist secret
typeof config.googleAnalyticsDisable == "boolean" &&
typeof config.googleAnalyticsGTag === "string" &&
typeof config.iamPermissionsBoundary === "string" &&
Expand Down
50 changes: 50 additions & 0 deletions lib/lambda/getSystemNotifs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { getSystemNotifs } from "./getSystemNotifs";
import * as util from "shared-utils";

vi.mock("shared-utils", () => ({
getExport: vi.fn(),
getSecret: vi.fn(),
}));

describe("notif handler", () => {
beforeEach(() => {
vi.resetAllMocks();
});

it("returns 200 and notifs if secret exists", async () => {
vi.stubEnv("notificationSecretArn", "test_secret");
vi.spyOn(util, "getSecret").mockImplementation(async () => "[]");
const result = await getSystemNotifs();
expect(result.statusCode).toBe(200);
expect(result.body).toBe("[]");
});

it("returns 200 and empty array if no notifs", async () => {
vi.stubEnv("notificationSecretArn", "test_secret");
vi.spyOn(util, "getSecret").mockImplementation(async () => null as unknown as string);
const result = await getSystemNotifs();
expect(result.statusCode).toBe(200);
expect(result.body).toBe("[]");
});

it("returns 502 with specific error", async () => {
vi.stubEnv("notificationSecretArn", "error");
vi.spyOn(util, "getSecret").mockImplementation(async () => {
throw new Error("test error");
});
const result = await getSystemNotifs();
expect(result.statusCode).toBe(502);
expect(JSON.parse(result.body).error).toBe("test error");
});

it("returns 502 with generic error", async () => {
vi.stubEnv("notificationSecretArn", undefined);
vi.spyOn(util, "getSecret").mockImplementation(async () => {
throw new Error();
});
const result = await getSystemNotifs();
expect(result.statusCode).toBe(502);
expect(JSON.parse(result.body).error).toBe("Internal server error");
});
});
23 changes: 23 additions & 0 deletions lib/lambda/getSystemNotifs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getSecret } from "shared-utils";
import { response } from "libs/handler-lib";

export const getSystemNotifs = async () => {
try {
const notifs = await getSecret(process.env.notificationSecretArn!);

return response({
statusCode: 200,
body: JSON.parse(notifs) || [],
});
} catch (error: any) {
console.error("Error:", error);
return response({
statusCode: 502,
body: {
error: error.message ? error.message : "Internal server error",
},
});
}
};

export const handler = getSystemNotifs;
13 changes: 13 additions & 0 deletions lib/lambda/processEmailsHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const capitatedInitial = "capitated-initial";
const withdrawRai = "withdraw-rai";
const respondToRai = "respond-to-rai";
const appk = "app-k";
const uploadSubsequentDocuments = "upload-subsequent-documents";

describe("process emails Handler", () => {
it.each([
Expand Down Expand Up @@ -191,6 +192,18 @@ describe("process emails Handler", () => {
withdrawRai,
WITHDRAW_RAI_ITEM_C,
],
[
`should send an email for ${uploadSubsequentDocuments} with ${Authority.CHIP_SPA}`,
Authority.CHIP_SPA,
uploadSubsequentDocuments,
WITHDRAW_RAI_ITEM_B,
],
[
`should send an email for ${uploadSubsequentDocuments} with ${Authority["1915c"]}`,
Authority["1915c"],
uploadSubsequentDocuments,
WITHDRAW_RAI_ITEM_C,
],
])("%s", async (_, auth, eventType, id) => {
const callback = vi.fn();
const secSPY = vi.spyOn(SESClient.prototype, "send");
Expand Down
5 changes: 3 additions & 2 deletions lib/lambda/sinkMainProcessors.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { describe, expect, it, vi, afterEach } from "vitest";
import { startOfDay } from "date-fns";
import { UTCDate } from "@date-fns/utc";
import {
insertNewSeatoolRecordsFromKafkaIntoMako,
insertOneMacRecordsFromKafkaIntoMako,
syncSeatoolRecordDatesFromKafkaWithMako,
} from "./sinkMainProcessors";
import { seatool } from "shared-types/opensearch/main";
import { offsetToUtc } from "shared-utils";
import { SEATOOL_STATUS, statusToDisplayToCmsUser, statusToDisplayToStateUser } from "shared-types";
import * as sink from "libs/sink-lib";
import * as os from "libs/opensearch-lib";
Expand Down Expand Up @@ -208,7 +209,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => {
stateStatus: expectation.stateStatus || statusToDisplayToStateUser[seatoolStatus],
changedDate: ISO_DATETIME,
makoChangedDate: ISO_DATETIME,
statusDate: offsetToUtc(new Date(TIMESTAMP)).toISOString(),
statusDate: startOfDay(new UTCDate(TIMESTAMP)).toISOString(),
submissionDate: ISO_DATETIME,
state: "VA",
origin: "OneMAC",
Expand Down
2 changes: 1 addition & 1 deletion lib/libs/email/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const emailTemplates: EmailTemplates = {
"capitated-renewal-state": EmailContent.newSubmission,
"respond-to-rai": EmailContent.respondToRai,
"app-k": EmailContent.newSubmission,
"upload-subsequent-documents": EmailContent.newSubmission,
"upload-subsequent-documents": EmailContent.uploadSubsequentDocuments,
};

// Create a type-safe lookup function
Expand Down
1 change: 1 addition & 0 deletions lib/packages/shared-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from "./guides";
export * from "./inputs";
export * from "./issue";
export * from "./lambda-events";
export * from "./notification";
export * as opensearch from "./opensearch";
export * from "./states";
export * from "./statusHelper";
Expand Down
9 changes: 9 additions & 0 deletions lib/packages/shared-types/notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface BannerNotification {
notifId: string;
header: string;
body: string;
buttonText?: string;
buttonLink?: string;
pubDate: string;
expDate?: string;
}
4 changes: 3 additions & 1 deletion lib/packages/shared-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"devDependencies": {},
"dependencies": {
"@18f/us-federal-holidays": "^4.0.0",
"moment-timezone": "^0.5.45",
"@date-fns/tz": "1.2.0",
"@date-fns/utc": "2.1.0",
"date-fns": "4.1.0",
"shared-types": "*",
"eslint-config-custom-server": "*",
"eslint-config-custom": "*"
Expand Down
66 changes: 25 additions & 41 deletions lib/packages/shared-utils/seatool-date-helper.test.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,67 @@
import { it, describe, expect } from "vitest";
import {
formatSeatoolDate,
getNextBusinessDayTimestamp,
offsetFromUtc,
offsetToUtc,
seaToolFriendlyTimestamp,
} from ".";
import { formatSeatoolDate, getNextBusinessDayTimestamp, seaToolFriendlyTimestamp } from ".";
import { format } from "date-fns";

describe("offsetToUtc", () => {
it("offsets given date to UTC", () => {
const originalDate = new Date("January 1, 2000 12:00:00");
const timezoneOffset = originalDate.getTimezoneOffset() * 60000; // in milliseconds
const expectedDate = new Date(originalDate.getTime() - timezoneOffset);
console.debug("originalDate: ", originalDate, "expectedDate: ", expectedDate);
expect(offsetToUtc(originalDate)).toEqual(expectedDate);
describe("seaToolFriendlyTimestamp", () => {
it("should convert date to a timestamp representing the date at midnight UTC time", () => {
const localDate = new Date("2025-01-23T17:01:42.000Z");
const expectedTimestamp = Date.parse("2025-01-23T00:00:00.000Z");
expect(seaToolFriendlyTimestamp(localDate)).toEqual(expectedTimestamp);
});
});

describe("offsetFromUtc", () => {
it("offsets UTC date to user's timezone", () => {
const originalDate = new Date("2000-01-01T12:00:00.000Z");
const timezoneOffset = originalDate.getTimezoneOffset() * 60000; // in milliseconds
const expectedDate = new Date(originalDate.getTime() + timezoneOffset);
console.debug("originalDate: ", originalDate, "expectedDate: ", expectedDate);
expect(offsetFromUtc(originalDate)).toEqual(expectedDate);
});
});

describe("seaToolFriendlyTimestamp", () => {
it("converts given date to a time string representing the given date", () => {
const originalDate = new Date("January 1, 2000 12:00:00");
const timezoneOffset = originalDate.getTimezoneOffset() * 60000; // in milliseconds
const expectedDate = new Date(originalDate.getTime() - timezoneOffset);
expect(seaToolFriendlyTimestamp(originalDate)).toEqual(expectedDate.getTime());
it("should return timestamp representing today at midnight UTC time", () => {
const todayDateStr = format(new Date(), "yyyy-MM-dd");
const expectedTimestamp = Date.parse(`${todayDateStr}T00:00:00.000Z`);
expect(seaToolFriendlyTimestamp()).toEqual(expectedTimestamp);
});
});

describe("formatSeatoolDate", () => {
it("formats a SEATool date to a user-friendly format", () => {
it("should format a SEATool date to a user-friendly format", () => {
const originalDate = new Date("2000-01-01T00:00:00.000Z");
expect(formatSeatoolDate(originalDate.toISOString())).toEqual("01/01/2000");
});

it("should convert time to UTC to handle timezone differences", () => {
const originalDate = new Date("Fri Dec 31 1999 19:00:00 GMT-0500 (Eastern Standard Time)");
expect(formatSeatoolDate(originalDate.toISOString())).toEqual("01/01/2000");
});
});

describe("getNextBusinessDayTimestamp", () => {
it("identifies weekenends", () => {
const testDate = new Date(2024, 0, 27, 12, 0, 0); // Saturday, noon, utc
it("identifies weekends", () => {
const testDate = new Date(Date.UTC(2024, 0, 27, 12, 0, 0)); // Saturday, noon, utc
const nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 29)); // Monday, midnight, utc
});

it("identifies holidays", () => {
const testDate = new Date(2024, 0, 15, 12, 0, 0); // MLK Day, a Monday
const testDate = new Date(Date.UTC(2024, 0, 15, 12, 0, 0)); // MLK Day, a Monday
const nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 16)); // Tuesday, midnight, utc
});

it("identifies submissions after 5pm eastern", () => {
const testDate = new Date(2024, 0, 17, 23, 0, 0); // Wednesday 11pm utc, Wednesday 6pm eastern
const testDate = new Date(Date.UTC(2024, 0, 17, 23, 0, 0)); // Wednesday 11pm utc, Wednesday 6pm eastern
const nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 18)); // Thursday, midnight, utc
});

it("identifies submissions before 5pm eastern", () => {
const testDate = new Date(2024, 0, 17, 10, 0, 0); // Wednesday 10am utc, Wednesday 5am eastern
const testDate = new Date(Date.UTC(2024, 0, 17, 10, 0, 0)); // Wednesday 10am utc, Wednesday 5am eastern
const nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 17)); // Wednesday, midnight, utc
});

it("handles combinations of rule violations", () => {
const testDate = new Date(2024, 0, 12, 23, 0, 0); // Friday 11pm utc, Friday 6pm eastern
const testDate = new Date(Date.UTC(2024, 0, 12, 23, 0, 0)); // Friday 11pm utc, Friday 6pm eastern
const nextDate = getNextBusinessDayTimestamp(testDate);
// Submission is after 5pm, Saturday is a weekend, Sunday is a weekend, and Monday is MLK Day
expect(nextDate).toEqual(Date.UTC(2024, 0, 16)); // Tuesday, midnight utc
});

// TODO: I dont know if its my time zone but this always fails for me in the MST
it.skip("identifies valid business days", () => {
const testDate = new Date(2024, 0, 9, 15, 0, 0); // Tuesday 3pm utc, Tuesday 8am eastern
it("identifies valid business days", () => {
const testDate = new Date(Date.UTC(2024, 0, 9, 15, 0, 0)); // Tuesday 3pm utc, Tuesday 8am eastern
const nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 9)); // Tuesday, midnight utc
});
Expand Down
Loading

0 comments on commit 77ca522

Please sign in to comment.