diff --git a/schema.graphqls b/schema.graphqls index 6296660..c5a6726 100644 --- a/schema.graphqls +++ b/schema.graphqls @@ -63,6 +63,8 @@ type Shuttle { type Query { systems: [System!]! system(id: ID): System + + isNotificationScheduled(input: NotificationInput!): Boolean } # Mutations diff --git a/src/resolvers/QueryResolvers.ts b/src/resolvers/QueryResolvers.ts index 8bb439d..98c8965 100644 --- a/src/resolvers/QueryResolvers.ts +++ b/src/resolvers/QueryResolvers.ts @@ -15,6 +15,10 @@ export const QueryResolvers: Resolvers = { name: system.name, id: system.id, }; + }, + isNotificationScheduled: async (_parent, args, contextValue, _info) => { + const notificationData = args.input; + return contextValue.notificationService.isNotificationScheduled(notificationData); } }, -} \ No newline at end of file +} diff --git a/src/services/NotificationService.ts b/src/services/NotificationService.ts index c562986..573e2fb 100644 --- a/src/services/NotificationService.ts +++ b/src/services/NotificationService.ts @@ -250,4 +250,28 @@ export class NotificationService { } return this.deviceIdsToDeliverTo[tuple.toString()].has(deviceId); } + + /** + * Return all scheduled notification for the given device ID. + * @param deviceId + */ + public async getAllScheduledNotificationsForDevice(deviceId: string): Promise { + const scheduledNotifications: ScheduledNotificationData[] = []; + + for (const key of Object.keys(this.deviceIdsToDeliverTo)) { + if (this.deviceIdsToDeliverTo[key].has(deviceId)) { + const tupleKey = TupleKey.fromExistingStringKey(key); + const shuttleId = tupleKey.tuple[0] + const stopId = tupleKey.tuple[1]; + + scheduledNotifications.push({ + shuttleId, + stopId, + deviceId, + }); + } + } + + return scheduledNotifications; + } } diff --git a/src/types/TupleKey.ts b/src/types/TupleKey.ts index 08dcfb4..f95f4f0 100644 --- a/src/types/TupleKey.ts +++ b/src/types/TupleKey.ts @@ -16,4 +16,9 @@ export class TupleKey { valueOf(): string { return this.strKey; } + + static fromExistingStringKey(strKey: string) { + const tuple = strKey.split(separator); + return new TupleKey(...tuple); + } } diff --git a/test/resolvers/QueryResolverTests.test.ts b/test/resolvers/QueryResolverTests.test.ts index af73aca..bb53d5b 100644 --- a/test/resolvers/QueryResolverTests.test.ts +++ b/test/resolvers/QueryResolverTests.test.ts @@ -2,6 +2,8 @@ import { describe, expect, it } from "@jest/globals"; import { generateMockSystems } from "../testHelpers/mockDataGenerators"; import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; import assert = require("node:assert"); +import { ScheduledNotificationData } from "../../src/services/NotificationService"; +import { addMockShuttleToRepository, addMockStopToRepository } from "../testHelpers/repositorySetupHelpers"; // See Apollo documentation for integration test guide // https://www.apollographql.com/docs/apollo-server/testing/testing @@ -93,4 +95,63 @@ describe("QueryResolvers", () => { expect(response.body.singleResult.data?.system).toBeNull(); }); }); + + describe("isNotificationScheduled", () => { + const query = ` + query IsNotificationScheduled($input: NotificationInput!) { + isNotificationScheduled(input: $input) + } + ` + + it("returns true if the notification is scheduled", async () => { + // Arrange + const shuttle = await addMockShuttleToRepository(context.repository, "1"); + const stop = await addMockStopToRepository(context.repository, "1") + + const notification: ScheduledNotificationData = { + shuttleId: shuttle.id, + stopId: stop.id, + deviceId: "1", + }; + await context.notificationService.scheduleNotification(notification); + + // Act + const response = await holder.testServer.executeOperation({ + query, + variables: { + input: { + ...notification, + }, + } + }, { + contextValue: context, + }); + + // Assert + assert(response.body.kind === "single"); + expect(response.body.singleResult.errors).toBeUndefined(); + expect(response.body.singleResult.data?.isNotificationScheduled).toBe(true); + }); + + it("returns false if the notification isn't scheduled", async () => { + // Act + const response = await holder.testServer.executeOperation({ + query, + variables: { + input: { + shuttleId: "1", + stopId: "1", + deviceId: "1", + }, + } + }, { + contextValue: context, + }); + + // Assert + assert(response.body.kind === "single"); + expect(response.body.singleResult.errors).toBeUndefined(); + expect(response.body.singleResult.data?.isNotificationScheduled).toBe(false); + }); + }); }); diff --git a/test/services/NotificationServiceTests.test.ts b/test/services/NotificationServiceTests.test.ts index 7f3cfcf..8517727 100644 --- a/test/services/NotificationServiceTests.test.ts +++ b/test/services/NotificationServiceTests.test.ts @@ -230,4 +230,38 @@ describe("NotificationService", () => { expect(http2.connect as jest.Mock).toHaveBeenCalledTimes(0); }); }); + + describe("getAllScheduledNotificationsForDevice", () => { + it("returns scheduled notifications for the device ID", async () => { + // Arrange + const shuttle1 = await addMockShuttleToRepository(repository, "1"); + const stop = await addMockStopToRepository(repository, "1"); + const { eta, notificationData1 } = generateNotificationDataAndEta(shuttle1, stop); + await notificationService.scheduleNotification(notificationData1); + + const shuttle2 = { + ...shuttle1, + id: "2", + } + await repository.addOrUpdateShuttle(shuttle2); + + const notificationData2 = { + ...notificationData1, + shuttleId: shuttle2.id, + } + await notificationService.scheduleNotification(notificationData2); + + // Act + const notifications = await notificationService.getAllScheduledNotificationsForDevice(notificationData1.deviceId); + + // Assert + expect(notifications.length).toBe(2); + }); + + it("returns an empty array if there are no notifications", async () => { + // Act + const notifications = await notificationService.getAllScheduledNotificationsForDevice("1"); + expect(notifications.length).toBe(0); + }); + }); }); diff --git a/test/types/TupleKeyTests.test.ts b/test/types/TupleKeyTests.test.ts index ac260dd..b04c3ec 100644 --- a/test/types/TupleKeyTests.test.ts +++ b/test/types/TupleKeyTests.test.ts @@ -28,4 +28,18 @@ describe("TupleKey", () => { expect(sampleObject[tupleKey1.toString()]).toEqual("value1"); expect(sampleObject[(new TupleKey("1", "2")).toString()]).toEqual("value1"); }); -}); \ No newline at end of file + + describe("fromExistingStringKey", () => { + it("creates a new TupleKey from an existing string key", () => { + const strKey = "hello|there"; + const tupleKey = TupleKey.fromExistingStringKey(strKey); + expect(tupleKey.toString()).toEqual(strKey); + }); + + it("creates an empty tuple if there is no string", () => { + const strKey = ""; + const tupleKey = TupleKey.fromExistingStringKey(strKey); + expect(tupleKey.toString()).toEqual(strKey); + }) + }) +});