diff --git a/lambda/datetime-util.ts b/lambda/datetime-util.ts new file mode 100644 index 0000000..8ba93d0 --- /dev/null +++ b/lambda/datetime-util.ts @@ -0,0 +1,24 @@ + +export function formatDate(date: Date, timezoneCode?: string): string { + console.log(date, timezoneCode); + return new Intl.DateTimeFormat("default", { + month: "short", + day: "numeric", + hour: "numeric", + minute: "numeric", + hour12: true, + timeZone: timezoneCode, + timeZoneName: "short", + }).format(date); +} + +export function determineThresholdDatetime(eventDate: Date): Date { + const thresholdHours = Number(process.env.NOTIFICATION_THRESHOLD_HRS); + const thresholdDatetime = new Date(eventDate.getTime()); + thresholdDatetime.setHours(eventDate.getHours() + thresholdHours); + return thresholdDatetime; +} + +export function hasThresholdTimePassed(thresholdDatetime: Date): boolean { + return Date.now() > thresholdDatetime.getTime(); +} \ No newline at end of file diff --git a/lambda/index.ts b/lambda/index.ts index b5e638f..ef77913 100644 --- a/lambda/index.ts +++ b/lambda/index.ts @@ -3,6 +3,7 @@ import { DeviceType } from "homebridge-lg-thinq/dist/lib/constants"; import { Device } from "homebridge-lg-thinq/dist/lib/Device"; import { URL } from "url"; import { ThinQApi } from "./api"; +import { determineThresholdDatetime, formatDate, hasThresholdTimePassed } from "./datetime-util"; export const handler = async (): Promise => { const region = process.env.AWS_REGION; @@ -20,17 +21,18 @@ export const handler = async (): Promise => { const mostRecentEvent = events[0]; const eventDate = new Date(Number(mostRecentEvent.sendDate) * 1000); const eventMessage = JSON.parse(mostRecentEvent.message) as EventMessage; - const formattedEventDate = formatDate(eventDate, dryer?.data.timezoneCode); + const formattedEventDate = formatDate( + eventDate, + dryer?.data.timezoneCode + ); + const thresholdDatetime = determineThresholdDatetime(eventDate); console.log(`Most recent event was at ${formattedEventDate}`); - - const thresholdHours = Number(process.env.NOTIFICATION_THRESHOLD_HRS); - const thresholdTime = new Date(); - thresholdTime.setHours(new Date().getHours() - thresholdHours); + console.log(`Threshold datetime is ${formatDate(thresholdDatetime, dryer?.data.timezoneCode)}`); if ( - eventDate < thresholdTime && - shouldSendRepeatNotification(thresholdTime) && + hasThresholdTimePassed(thresholdDatetime) && + shouldSendRepeatNotification(thresholdDatetime) && isWasherCycleFinished(eventMessage) && !(await wasLatestWashTubClean(api, clientId, eventMessage)) ) { @@ -48,18 +50,6 @@ export const handler = async (): Promise => { } }; -function formatDate(date: Date, timezoneCode?: string): string { - return new Intl.DateTimeFormat("default", { - month: "short", - day: "numeric", - hour: "numeric", - minute: "numeric", - hour12: true, - timeZone: timezoneCode, - timeZoneName: "short", - }).format(date); -} - async function getAppSecrets( region = "us-east-1" ): Promise> { @@ -165,17 +155,17 @@ function isDryerOff(dryer?: Device): boolean { ); } -function shouldSendRepeatNotification(thresholdTime: Date): boolean { +export function shouldSendRepeatNotification(thresholdDatetime: Date): boolean { const notificationFreqHrs = Number(process.env.NOTIFICATION_FREQ_HRS); const maxNotifications = Number(process.env.MAX_NOTIFICATIONS); - const msSinceThreshold = Date.now() - thresholdTime.getTime(); + const msSinceThreshold = Date.now() - thresholdDatetime.getTime(); const hoursSinceThreshold = msSinceThreshold / (60 * 60 * 1000); console.log({ hoursSinceThreshold, notificationFreqHrs, maxNotifications }); return ( hoursSinceThreshold % notificationFreqHrs < 1 && - Math.floor(hoursSinceThreshold / notificationFreqHrs) <= maxNotifications + Math.floor(hoursSinceThreshold / notificationFreqHrs) < maxNotifications ); } diff --git a/test/datetime-util.test.ts b/test/datetime-util.test.ts new file mode 100644 index 0000000..aa1f451 --- /dev/null +++ b/test/datetime-util.test.ts @@ -0,0 +1,15 @@ +import { determineThresholdDatetime } from "../lambda/datetime-util"; + +describe("determineThresholdDatetime", () => { + test("should return `NOTIFICATION_THRESHOLD_HRS` after event date", async () => { + // given + process.env.NOTIFICATION_THRESHOLD_HRS = "3"; + const eventDate = new Date("2021-11-01T12:00:00.000Z"); + + // when + const result = determineThresholdDatetime(eventDate); + + // then + expect(result).toEqual(new Date("2021-11-01T15:00:00.000Z")); + }); +}); diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 0000000..e458f33 --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,87 @@ +import { shouldSendRepeatNotification } from "../lambda/index"; + +describe("test shouldSendRepeatNotification given `NOTIFICATION_FREQ_HRS`=3 and `MAX_NOTIFICATIONS`=2", () => { + + beforeEach(() => { + process.env.NOTIFICATION_FREQ_HRS = "3"; + process.env.MAX_NOTIFICATIONS = "2"; + }); + + test("should send repeat notification given washer finished 3 hours ago", async () => { + // given + const thresholdTime = new Date(); + thresholdTime.setHours(thresholdTime.getHours() - 3); + thresholdTime.setMinutes(thresholdTime.getMinutes() - 20); + + // when + const result = shouldSendRepeatNotification(thresholdTime); + + // then + expect(result).toBe(true); + }); + + test("should send repeat notification given washer finished 6 hours ago", async () => { + // given + const thresholdTime = new Date(); + thresholdTime.setHours(thresholdTime.getHours() - 6); + thresholdTime.setMinutes(thresholdTime.getMinutes() - 20); + + // when + const result = shouldSendRepeatNotification(thresholdTime); + + // then + expect(result).toBe(true); + }); + + test("should not send repeat notification given washer finished 9 hours ago", async () => { + // given + const thresholdTime = new Date(); + thresholdTime.setHours(thresholdTime.getHours() - 9); + thresholdTime.setMinutes(thresholdTime.getMinutes() - 20); + + // when + const result = shouldSendRepeatNotification(thresholdTime); + + // then + expect(result).toBe(false); + }); + + test("should not send repeat notification given washer finished 9 hours ago", async () => { + // given + const thresholdTime = new Date(); + thresholdTime.setHours(thresholdTime.getHours() - 9); + thresholdTime.setMinutes(thresholdTime.getMinutes() - 20); + + // when + const result = shouldSendRepeatNotification(thresholdTime); + + // then + expect(result).toBe(false); + }); + + test("should not send repeat notification given washer finished 4 hours ago", async () => { + // given + const thresholdTime = new Date(); + thresholdTime.setHours(thresholdTime.getHours() - 4); + thresholdTime.setMinutes(thresholdTime.getMinutes() - 20); + + // when + const result = shouldSendRepeatNotification(thresholdTime); + + // then + expect(result).toBe(false); + }); + + test("should not send repeat notification given washer finished 7 hours ago", async () => { + // given + const thresholdTime = new Date(); + thresholdTime.setHours(thresholdTime.getHours() - 7); + thresholdTime.setMinutes(thresholdTime.getMinutes() - 20); + + // when + const result = shouldSendRepeatNotification(thresholdTime); + + // then + expect(result).toBe(false); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 9f8e8be..d25957c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,10 +21,18 @@ "strictPropertyInitialization": false, "typeRoots": [ "./node_modules/@types" - ] + ], + "skipLibCheck": true }, + "include": [ + "bin/**/*.ts", + "lambda/**/*.ts", + "lib/**/*.ts", + "test/**/*.ts" + ], "exclude": [ "node_modules", - "cdk.out" + "cdk.out", + ".github" ] }