From 82a70ea04ed551df8d18cb11acfe26b3c8c03065 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:12:19 -0800 Subject: [PATCH 01/27] add notification service to server context --- src/ServerContext.ts | 4 +++- src/index.ts | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ServerContext.ts b/src/ServerContext.ts index c380583..7acfab3 100644 --- a/src/ServerContext.ts +++ b/src/ServerContext.ts @@ -1,5 +1,7 @@ import { GetterRepository } from "./repositories/GetterRepository"; +import { NotificationService } from "./services/NotificationService"; export interface ServerContext { repository: GetterRepository; -} \ No newline at end of file + notificationService: NotificationService; +} diff --git a/src/index.ts b/src/index.ts index b90d962..9e93dcc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { MergedResolvers } from "./MergedResolvers"; import { ServerContext } from "./ServerContext"; import { UnoptimizedInMemoryRepository } from "./repositories/UnoptimizedInMemoryRepository"; import { TimedApiBasedRepositoryLoader } from "./loaders/TimedApiBasedRepositoryLoader"; +import { NotificationService } from "./services/NotificationService"; const typeDefs = readFileSync("./schema.graphqls", "utf8"); @@ -16,13 +17,13 @@ async function main() { }); const repository = new UnoptimizedInMemoryRepository(); - // await loadTestData(repository); - const repositoryDataUpdater = new TimedApiBasedRepositoryLoader( repository ); await repositoryDataUpdater.start(); + const notificationService = new NotificationService(repository); + const { url } = await startStandaloneServer(server, { listen: { port: process.env.PORT ? parseInt(process.env.PORT) : 4000, @@ -30,6 +31,7 @@ async function main() { context: async ({ req, res }) => { return { repository, + notificationService, } }, }); From d730937b29dc4df4f786558f94e3349e7ca7892b Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:27:06 -0800 Subject: [PATCH 02/27] add notification support in schema --- package-lock.json | 6 ++++-- schema.graphqls | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index f9544cc..4d485f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "dependencies": { "@apollo/server": "^4.11.2", - "@types/jsonwebtoken": "^9.0.8", "graphql": "^16.10.0", "jsonwebtoken": "^9.0.2" }, @@ -18,6 +17,7 @@ "@graphql-codegen/typescript": "4.1.2", "@graphql-codegen/typescript-resolvers": "4.4.1", "@jest/globals": "^29.7.0", + "@types/jsonwebtoken": "^9.0.8", "@types/node": "^22.10.2", "jest": "^29.7.0", "ts-jest": "^29.2.5", @@ -3571,6 +3571,7 @@ "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.8.tgz", "integrity": "sha512-7fx54m60nLFUVYlxAB1xpe9CBWX2vSrk50Y6ogRJ1v5xxtba7qXTg5BgYDN5dq+yuQQ9HaVlHJyAAt1/mxryFg==", + "dev": true, "dependencies": { "@types/ms": "*", "@types/node": "*" @@ -3589,7 +3590,8 @@ "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true }, "node_modules/@types/node": { "version": "22.10.2", diff --git a/schema.graphqls b/schema.graphqls index 3d26683..ab2ab99 100644 --- a/schema.graphqls +++ b/schema.graphqls @@ -1,8 +1,4 @@ -type Query { - systems: [System!]! - system(id: ID): System -} - +# Base types type System { id: ID! name: String! @@ -62,3 +58,34 @@ type Shuttle { etas: [ETA!] eta(forStopId: ID): ETA } + +# Queries +type Query { + systems: [System!]! + system(id: ID): System +} + +# Mutations +type Mutation { + scheduleNotification(input: NotificationInput): NotificationResponse + cancelNotification(input: NotificationInput): NotificationResponse + cancelAllNotifications(deviceId: ID!): NotificationResponse +} + +input NotificationInput { + deviceId: ID! + shuttleId: ID! + stopId: ID! +} + +type NotificationResponse { + success: Boolean! + message: String! + data: Notification +} + +type Notification { + deviceId: ID! + shuttleId: ID! + stopId: ID! +} From 2116387203848a8b4590770a532bae37ca57d127 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:35:50 -0800 Subject: [PATCH 03/27] add stubs for mutations --- src/resolvers/MutationResolvers.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/resolvers/MutationResolvers.ts diff --git a/src/resolvers/MutationResolvers.ts b/src/resolvers/MutationResolvers.ts new file mode 100644 index 0000000..c6d61db --- /dev/null +++ b/src/resolvers/MutationResolvers.ts @@ -0,0 +1,16 @@ +import { NotificationInput, Resolvers } from "../generated/graphql"; +import { ServerContext } from "../ServerContext"; + +export const MutationResolvers: Resolvers = { + Mutation: { + scheduleNotification: async (_parent, args, context, _info) => { + + }, + cancelNotification: async (_parent, args, context, _info) => { + + }, + cancelAllNotifications: async (_parent, args, context, _info) => { + + }, + }, +} From 588d433fa3f8996bf68c92f62ea617662069faab Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:36:46 -0800 Subject: [PATCH 04/27] add placeholder responses to get typescript to stop screaming --- src/resolvers/MutationResolvers.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/resolvers/MutationResolvers.ts b/src/resolvers/MutationResolvers.ts index c6d61db..a050bb1 100644 --- a/src/resolvers/MutationResolvers.ts +++ b/src/resolvers/MutationResolvers.ts @@ -1,16 +1,28 @@ -import { NotificationInput, Resolvers } from "../generated/graphql"; +import { NotificationInput, NotificationResponse, Resolvers } from "../generated/graphql"; import { ServerContext } from "../ServerContext"; export const MutationResolvers: Resolvers = { Mutation: { scheduleNotification: async (_parent, args, context, _info) => { - + const response: NotificationResponse = { + message: "Not implemented", + success: false + } + return response; }, cancelNotification: async (_parent, args, context, _info) => { - + const response: NotificationResponse = { + message: "Not implemented", + success: false + } + return response; }, cancelAllNotifications: async (_parent, args, context, _info) => { - + const response: NotificationResponse = { + message: "Not implemented", + success: false + } + return response; }, }, } From d54539e65b55dd8478afb0d1fbcae22dfa9ddbaa Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:41:35 -0800 Subject: [PATCH 05/27] add test cases --- test/resolvers/MutationResolverTests.test.ts | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 test/resolvers/MutationResolverTests.test.ts diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts new file mode 100644 index 0000000..4d52985 --- /dev/null +++ b/test/resolvers/MutationResolverTests.test.ts @@ -0,0 +1,22 @@ +import { setupTestServerContext } from "../testHelpers/apolloTestServerHelpers"; +import { describe, it } from "@jest/globals"; + +describe("MutationResolvers", () => { + const context = setupTestServerContext() + + describe("scheduleNotification", () => { + it("adds a notification to the notification service", async () => { + + }); + + it("ensures that notifications when the ETA changes", async () => { + + }); + }); + + describe("cancelNotification", () => { + it("removes the notification from the notification service", async () => { + + }); + }); +}); From f1f1d5c971b865a8ac4d58514eea3c5126dffc8a Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:42:12 -0800 Subject: [PATCH 06/27] remove cancelAllNotifications resolver --- schema.graphqls | 1 - src/resolvers/MutationResolvers.ts | 7 ------- 2 files changed, 8 deletions(-) diff --git a/schema.graphqls b/schema.graphqls index ab2ab99..da87864 100644 --- a/schema.graphqls +++ b/schema.graphqls @@ -69,7 +69,6 @@ type Query { type Mutation { scheduleNotification(input: NotificationInput): NotificationResponse cancelNotification(input: NotificationInput): NotificationResponse - cancelAllNotifications(deviceId: ID!): NotificationResponse } input NotificationInput { diff --git a/src/resolvers/MutationResolvers.ts b/src/resolvers/MutationResolvers.ts index a050bb1..9332e8b 100644 --- a/src/resolvers/MutationResolvers.ts +++ b/src/resolvers/MutationResolvers.ts @@ -17,12 +17,5 @@ export const MutationResolvers: Resolvers = { } return response; }, - cancelAllNotifications: async (_parent, args, context, _info) => { - const response: NotificationResponse = { - message: "Not implemented", - success: false - } - return response; - }, }, } From 43f0ef27fc3de70ba92f231a2c05318863f706cb Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:43:33 -0800 Subject: [PATCH 07/27] fix typo --- test/resolvers/MutationResolverTests.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index 4d52985..199b1c4 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -9,7 +9,7 @@ describe("MutationResolvers", () => { }); - it("ensures that notifications when the ETA changes", async () => { + it("ensures that notifications fire when the ETA changes to below threshold", async () => { }); }); From c79a3eac7332e0ddd1e6c7850552a14a78fdac45 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:51:41 -0800 Subject: [PATCH 08/27] change method signatures for repository helpers to GetterSetterResponse interface --- test/testHelpers/repositorySetupHelpers.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/testHelpers/repositorySetupHelpers.ts b/test/testHelpers/repositorySetupHelpers.ts index d9e48e3..4c95363 100644 --- a/test/testHelpers/repositorySetupHelpers.ts +++ b/test/testHelpers/repositorySetupHelpers.ts @@ -1,4 +1,3 @@ -import { UnoptimizedInMemoryRepository } from "../../src/repositories/UnoptimizedInMemoryRepository"; import { generateMockEtas, generateMockRoutes, @@ -6,8 +5,9 @@ import { generateMockStops, generateMockSystems } from "./mockDataGenerators"; +import { GetterSetterRepository } from "../../src/repositories/GetterSetterRepository"; -export async function addMockSystemToRepository(repository: UnoptimizedInMemoryRepository) { +export async function addMockSystemToRepository(repository: GetterSetterRepository) { const mockSystems = generateMockSystems(); const mockSystem = mockSystems[0]; mockSystem.id = "1"; @@ -16,7 +16,7 @@ export async function addMockSystemToRepository(repository: UnoptimizedInMemoryR return mockSystem; } -export async function addMockRouteToRepository(repository: UnoptimizedInMemoryRepository, systemId: string) { +export async function addMockRouteToRepository(repository: GetterSetterRepository, systemId: string) { const mockRoutes = generateMockRoutes(); const mockRoute = mockRoutes[0]; mockRoute.systemId = systemId; @@ -25,7 +25,7 @@ export async function addMockRouteToRepository(repository: UnoptimizedInMemoryRe return mockRoute; } -export async function addMockStopToRepository(repository: UnoptimizedInMemoryRepository, systemId: string) { +export async function addMockStopToRepository(repository: GetterSetterRepository, systemId: string) { const mockStops = generateMockStops(); const mockStop = mockStops[0]; mockStop.systemId = systemId; @@ -34,7 +34,7 @@ export async function addMockStopToRepository(repository: UnoptimizedInMemoryRep return mockStop; } -export async function addMockShuttleToRepository(repository: UnoptimizedInMemoryRepository, systemId: string) { +export async function addMockShuttleToRepository(repository: GetterSetterRepository, systemId: string) { const mockShuttles = generateMockShuttles(); const mockShuttle = mockShuttles[0]; mockShuttle.systemId = systemId; @@ -42,7 +42,7 @@ export async function addMockShuttleToRepository(repository: UnoptimizedInMemory return mockShuttle; } -export async function addMockEtaToRepository(repository: UnoptimizedInMemoryRepository, stopId: string, shuttleId: string) { +export async function addMockEtaToRepository(repository: GetterSetterRepository, stopId: string, shuttleId: string) { const etas = generateMockEtas(); const expectedEta = etas[0]; expectedEta.stopId = stopId; From 814f2c65848523f1dc66b49d6608ad9b17e645e2 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:52:07 -0800 Subject: [PATCH 09/27] change repository type to GetterSetterRepository to account for mutations --- src/ServerContext.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ServerContext.ts b/src/ServerContext.ts index 7acfab3..6579453 100644 --- a/src/ServerContext.ts +++ b/src/ServerContext.ts @@ -1,7 +1,7 @@ -import { GetterRepository } from "./repositories/GetterRepository"; import { NotificationService } from "./services/NotificationService"; +import { GetterSetterRepository } from "./repositories/GetterSetterRepository"; export interface ServerContext { - repository: GetterRepository; + repository: GetterSetterRepository; notificationService: NotificationService; } From b918bf7a67856b5be3856aedaf7b4c3e06d5a2e7 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:56:00 -0800 Subject: [PATCH 10/27] update test helpers to consolidate ServerContext creation into one method --- test/resolvers/EtaResolverTests.test.ts | 7 ++--- test/resolvers/MutationResolverTests.test.ts | 4 +-- .../OrderedStopResolverTests.test.ts | 11 ++++---- test/resolvers/QueryResolverTests.test.ts | 11 ++++---- test/resolvers/RouteResolverTests.test.ts | 9 ++++--- test/resolvers/ShuttleResolverTests.test.ts | 15 ++++++----- test/resolvers/StopResolverTests.test.ts | 7 ++--- test/resolvers/SystemResolverTests.test.ts | 13 ++++----- test/testHelpers/apolloTestServerHelpers.ts | 27 ++++++++++++++----- 9 files changed, 63 insertions(+), 41 deletions(-) diff --git a/test/resolvers/EtaResolverTests.test.ts b/test/resolvers/EtaResolverTests.test.ts index 73c9770..98c2e52 100644 --- a/test/resolvers/EtaResolverTests.test.ts +++ b/test/resolvers/EtaResolverTests.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; -import { setupTestServerContext } from "../testHelpers/apolloTestServerHelpers"; +import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; import { IEta, IShuttle, IStop, ISystem } from "../../src/entities/entities"; import { addMockEtaToRepository, addMockShuttleToRepository, @@ -9,6 +9,7 @@ import { import assert = require("node:assert"); describe("EtaResolvers", () => { + const holder = setupTestServerHolder(); const context = setupTestServerContext(); let mockSystem: ISystem; @@ -24,7 +25,7 @@ describe("EtaResolvers", () => { }); async function getResponseForEtaQuery(query: string) { - const response = await context.testServer.executeOperation({ + const response = await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -87,4 +88,4 @@ describe("EtaResolvers", () => { expect(eta.shuttle.id).toEqual(expectedEta.shuttleId); }); }); -}); \ No newline at end of file +}); diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index 199b1c4..4731199 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -1,8 +1,8 @@ -import { setupTestServerContext } from "../testHelpers/apolloTestServerHelpers"; import { describe, it } from "@jest/globals"; +import { setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; describe("MutationResolvers", () => { - const context = setupTestServerContext() + const testServerHolder = setupTestServerHolder() describe("scheduleNotification", () => { it("adds a notification to the notification service", async () => { diff --git a/test/resolvers/OrderedStopResolverTests.test.ts b/test/resolvers/OrderedStopResolverTests.test.ts index 11c46e4..0d047ac 100644 --- a/test/resolvers/OrderedStopResolverTests.test.ts +++ b/test/resolvers/OrderedStopResolverTests.test.ts @@ -1,11 +1,12 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; -import { setupTestServerContext } from "../testHelpers/apolloTestServerHelpers"; +import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; import { IRoute, IStop, ISystem } from "../../src/entities/entities"; import { generateMockOrderedStops, generateMockStops } from "../testHelpers/mockDataGenerators"; import { addMockRouteToRepository, addMockSystemToRepository } from "../testHelpers/repositorySetupHelpers"; import assert = require("node:assert"); describe("OrderedStopResolvers", () => { + const holder = setupTestServerHolder(); const context = setupTestServerContext(); let mockSystem: ISystem; @@ -59,7 +60,7 @@ describe("OrderedStopResolvers", () => { } `; - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -131,7 +132,7 @@ describe("OrderedStopResolvers", () => { } `; - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -206,7 +207,7 @@ describe("OrderedStopResolvers", () => { } `; - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -256,7 +257,7 @@ describe("OrderedStopResolvers", () => { } `; - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, diff --git a/test/resolvers/QueryResolverTests.test.ts b/test/resolvers/QueryResolverTests.test.ts index 73f8e83..af73aca 100644 --- a/test/resolvers/QueryResolverTests.test.ts +++ b/test/resolvers/QueryResolverTests.test.ts @@ -1,12 +1,13 @@ import { describe, expect, it } from "@jest/globals"; import { generateMockSystems } from "../testHelpers/mockDataGenerators"; -import { setupTestServerContext } from "../testHelpers/apolloTestServerHelpers"; +import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; import assert = require("node:assert"); // See Apollo documentation for integration test guide // https://www.apollographql.com/docs/apollo-server/testing/testing describe("QueryResolvers", () => { + const holder = setupTestServerHolder(); const context = setupTestServerContext(); async function addMockSystems() { @@ -30,7 +31,7 @@ describe("QueryResolvers", () => { } `; - const response = await context.testServer.executeOperation({ + const response = await holder.testServer.executeOperation({ query, }, { contextValue: { @@ -59,7 +60,7 @@ describe("QueryResolvers", () => { const systems = await addMockSystems(); const systemToGet = systems[1]; - const response = await context.testServer.executeOperation({ + const response = await holder.testServer.executeOperation({ query, variables: { id: systemToGet.id, @@ -76,7 +77,7 @@ describe("QueryResolvers", () => { }); it("returns null if there is no system", async () => { - const response = await context.testServer.executeOperation({ + const response = await holder.testServer.executeOperation({ query, variables: { id: "nonexistent-id", @@ -92,4 +93,4 @@ describe("QueryResolvers", () => { expect(response.body.singleResult.data?.system).toBeNull(); }); }); -}); \ No newline at end of file +}); diff --git a/test/resolvers/RouteResolverTests.test.ts b/test/resolvers/RouteResolverTests.test.ts index ba5b403..0279a6e 100644 --- a/test/resolvers/RouteResolverTests.test.ts +++ b/test/resolvers/RouteResolverTests.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; -import { setupTestServerContext } from "../testHelpers/apolloTestServerHelpers"; +import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; import { addMockRouteToRepository, addMockStopToRepository, @@ -10,6 +10,7 @@ import { IRoute, IStop, ISystem } from "../../src/entities/entities"; import assert = require("node:assert"); describe("RouteResolvers", () => { + const holder = setupTestServerHolder(); const context = setupTestServerContext(); let mockSystem: ISystem; @@ -40,7 +41,7 @@ describe("RouteResolvers", () => { } `; - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -95,7 +96,7 @@ describe("RouteResolvers", () => { } `; - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -145,4 +146,4 @@ describe("RouteResolvers", () => { expect(orderedStop).toBeNull(); }); }); -}); \ No newline at end of file +}); diff --git a/test/resolvers/ShuttleResolverTests.test.ts b/test/resolvers/ShuttleResolverTests.test.ts index 0811c3b..b2b3e81 100644 --- a/test/resolvers/ShuttleResolverTests.test.ts +++ b/test/resolvers/ShuttleResolverTests.test.ts @@ -1,12 +1,13 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; import { generateMockEtas, generateMockRoutes } from "../testHelpers/mockDataGenerators"; import { IShuttle, ISystem } from "../../src/entities/entities"; -import { setupTestServerContext } from "../testHelpers/apolloTestServerHelpers"; +import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; import { addMockShuttleToRepository, addMockSystemToRepository } from "../testHelpers/repositorySetupHelpers"; import assert = require("node:assert"); describe("ShuttleResolvers", () => { + const holder = setupTestServerHolder(); const context = setupTestServerContext(); let mockSystem: ISystem; @@ -47,7 +48,7 @@ describe("ShuttleResolvers", () => { const mockEta = etas[1]; // Act - const response = await context.testServer.executeOperation({ + const response = await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -68,7 +69,7 @@ describe("ShuttleResolvers", () => { }); it("returns null if it doesn't exist", async () => { - const response = await context.testServer.executeOperation({ + const response = await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -106,7 +107,7 @@ describe("ShuttleResolvers", () => { it("returns associated ETAs if they exist for the shuttle", async () => { const etas = await addMockEtas(mockShuttle.id); - const response = await context.testServer.executeOperation({ + const response = await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -125,7 +126,7 @@ describe("ShuttleResolvers", () => { }); it("returns empty array if no ETAs exist", async () => { - const response = await context.testServer.executeOperation({ + const response = await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -164,7 +165,7 @@ describe("ShuttleResolvers", () => { ` async function getResponseForQuery() { - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -197,4 +198,4 @@ describe("ShuttleResolvers", () => { }); }); -}); \ No newline at end of file +}); diff --git a/test/resolvers/StopResolverTests.test.ts b/test/resolvers/StopResolverTests.test.ts index 2a044b1..fdda62b 100644 --- a/test/resolvers/StopResolverTests.test.ts +++ b/test/resolvers/StopResolverTests.test.ts @@ -1,11 +1,12 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; -import { setupTestServerContext } from "../testHelpers/apolloTestServerHelpers"; +import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; import { generateMockEtas, generateMockOrderedStops } from "../testHelpers/mockDataGenerators"; import { IStop, ISystem } from "../../src/entities/entities"; import { addMockStopToRepository, addMockSystemToRepository } from "../testHelpers/repositorySetupHelpers"; import assert = require("node:assert"); describe("StopResolvers", () => { + const holder = setupTestServerHolder(); const context = setupTestServerContext(); let mockStop: IStop; @@ -17,7 +18,7 @@ describe("StopResolvers", () => { }) async function getResponseForQuery(query: string) { - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -105,4 +106,4 @@ describe("StopResolvers", () => { expect((response.body.singleResult.data as any).system.stop.etas).toHaveLength(0); }); }); -}); \ No newline at end of file +}); diff --git a/test/resolvers/SystemResolverTests.test.ts b/test/resolvers/SystemResolverTests.test.ts index 7020075..b7f3f5d 100644 --- a/test/resolvers/SystemResolverTests.test.ts +++ b/test/resolvers/SystemResolverTests.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; -import { setupTestServerContext } from "../testHelpers/apolloTestServerHelpers"; +import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; import { generateMockRoutes, generateMockShuttles, generateMockStops } from "../testHelpers/mockDataGenerators"; import { addMockRouteToRepository, @@ -11,6 +11,7 @@ import { ISystem } from "../../src/entities/entities"; import assert = require("node:assert"); describe("SystemResolvers", () => { + const holder = setupTestServerHolder(); const context = setupTestServerContext(); let mockSystem: ISystem; @@ -21,7 +22,7 @@ describe("SystemResolvers", () => { // TODO: Consolidate these into one single method taking an object async function getResponseFromQueryNeedingSystemId(query: string) { - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -102,7 +103,7 @@ describe("SystemResolvers", () => { } `; - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -169,7 +170,7 @@ describe("SystemResolvers", () => { } `; - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -237,7 +238,7 @@ describe("SystemResolvers", () => { } `; - return await context.testServer.executeOperation({ + return await holder.testServer.executeOperation({ query, variables: { systemId: mockSystem.id, @@ -318,4 +319,4 @@ describe("SystemResolvers", () => { expect(shuttles.length === expectedShuttles.length); }); }); -}); \ No newline at end of file +}); diff --git a/test/testHelpers/apolloTestServerHelpers.ts b/test/testHelpers/apolloTestServerHelpers.ts index 78b322f..e92dd86 100644 --- a/test/testHelpers/apolloTestServerHelpers.ts +++ b/test/testHelpers/apolloTestServerHelpers.ts @@ -4,6 +4,7 @@ import { MergedResolvers } from "../../src/MergedResolvers"; import { UnoptimizedInMemoryRepository } from "../../src/repositories/UnoptimizedInMemoryRepository"; import { beforeEach } from "@jest/globals"; import { ServerContext } from "../../src/ServerContext"; +import { NotificationService } from "../../src/services/NotificationService"; function setUpTestServer() { @@ -17,14 +18,28 @@ function setUpTestServer() { } export function setupTestServerContext() { - // @ts-ignore - const context: { testServer: ApolloServer; repository: UnoptimizedInMemoryRepository } = {}; + const context: { [key: string] : any } = {}; beforeEach(() => { - context.testServer = setUpTestServer(); context.repository = new UnoptimizedInMemoryRepository(); + context.notificationService = new NotificationService(context.repository); }); - // Return a reference, not destructured values - return context; -} \ No newline at end of file + return context as ServerContext; +} + +/** + * Returns an object which holds a test server. + * This server is reset before every test. + * Tests should keep a reference to the holder object, + * and not destructure it. + */ +export function setupTestServerHolder() { + const holder: { [key: string]: any } = {}; + + beforeEach(() => { + holder.testServer = setUpTestServer(); + }); + + return holder as { testServer: ApolloServer }; +} From dccee459c370f1175561bdf0fa03ee3a5551e304 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 10:56:36 -0800 Subject: [PATCH 11/27] add note on setupTestServerContext --- test/testHelpers/apolloTestServerHelpers.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/testHelpers/apolloTestServerHelpers.ts b/test/testHelpers/apolloTestServerHelpers.ts index e92dd86..a8af9d4 100644 --- a/test/testHelpers/apolloTestServerHelpers.ts +++ b/test/testHelpers/apolloTestServerHelpers.ts @@ -17,6 +17,10 @@ function setUpTestServer() { }); } +/** + * Returns a `ServerContext` object which can be passed to requests + * for testing. + */ export function setupTestServerContext() { const context: { [key: string] : any } = {}; From 8ac71090e4db49a7e217b2ad0eb274e5de6fd4db Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:01:27 -0800 Subject: [PATCH 12/27] make input mandatory --- schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schema.graphqls b/schema.graphqls index da87864..b606eb8 100644 --- a/schema.graphqls +++ b/schema.graphqls @@ -67,8 +67,8 @@ type Query { # Mutations type Mutation { - scheduleNotification(input: NotificationInput): NotificationResponse - cancelNotification(input: NotificationInput): NotificationResponse + scheduleNotification(input: NotificationInput!): NotificationResponse + cancelNotification(input: NotificationInput!): NotificationResponse } input NotificationInput { From 0b9759aff19f15162449e1c4247f6c51fc2d97d3 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:18:05 -0800 Subject: [PATCH 13/27] update test cases and implement add notification test --- test/resolvers/MutationResolverTests.test.ts | 54 ++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index 4731199..9e8b887 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -1,15 +1,61 @@ -import { describe, it } from "@jest/globals"; -import { setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; +import { describe, expect, it } from "@jest/globals"; +import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; +import assert = require("node:assert"); +import { + addMockShuttleToRepository, + addMockStopToRepository, + addMockSystemToRepository +} from "../testHelpers/repositorySetupHelpers"; describe("MutationResolvers", () => { - const testServerHolder = setupTestServerHolder() + const holder = setupTestServerHolder() + const context = setupTestServerContext(); describe("scheduleNotification", () => { + const query = ` + mutation ScheduleNotification($input: NotificationInput!) { + scheduleNotification(input: $input) { + success + message + notification { + deviceId + shuttleId + stopId + } + } + } + ` + it("adds a notification to the notification service", async () => { + const system = await addMockSystemToRepository(context.repository); + const shuttle = await addMockShuttleToRepository(context.repository, system.id); + const stop = await addMockStopToRepository(context.repository, system.id); + + const notificationInput = { + deviceId: "1", + shuttleId: shuttle.id, + stopId: stop.id, + }; + const response = await holder.testServer.executeOperation({ + query, + variables: { + input: notificationInput, + } + }); + + assert(response.body.kind === "single"); + expect(response.body.singleResult.errors).toBeUndefined(); + + const returnedData = response.body.singleResult.data; + expect(returnedData?.success).toBe(true); + expect(returnedData?.notification).toStrictEqual(notificationInput); + }); + + it("fails if the shuttle ID doesn't exist", async () => { }); - it("ensures that notifications fire when the ETA changes to below threshold", async () => { + it("fails if the stop ID doesn't exist", async () => { }); }); From 5687f7f60030df278602b4fbce9e8f123c345dd4 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:19:58 -0800 Subject: [PATCH 14/27] add second test if shuttle ID doesn't exist in repository --- test/resolvers/MutationResolverTests.test.ts | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index 9e8b887..f7342fd 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -41,6 +41,8 @@ describe("MutationResolvers", () => { variables: { input: notificationInput, } + }, { + contextValue: context, }); assert(response.body.kind === "single"); @@ -52,7 +54,26 @@ describe("MutationResolvers", () => { }); it("fails if the shuttle ID doesn't exist", async () => { + const system = await addMockSystemToRepository(context.repository); + const stop = await addMockStopToRepository(context.repository, system.id); + const notificationInput = { + deviceId: "1", + shuttleId: "1", + stopId: stop.id, + } + const response = await holder.testServer.executeOperation({ + query, + variables: { + input: notificationInput, + } + }, { + contextValue: context + }); + + assert(response.body.kind === "single"); + expect(response.body.singleResult.errors).toBeUndefined(); + expect(response.body.singleResult.data?.success).toBe(false); }); it("fails if the stop ID doesn't exist", async () => { From 3a51d15e63b87f9b6a36100355e60438c7a982c4 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:21:54 -0800 Subject: [PATCH 15/27] fix the resolver result --- test/resolvers/MutationResolverTests.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index f7342fd..72f4c9f 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -17,7 +17,7 @@ describe("MutationResolvers", () => { scheduleNotification(input: $input) { success message - notification { + data { deviceId shuttleId stopId @@ -48,9 +48,9 @@ describe("MutationResolvers", () => { assert(response.body.kind === "single"); expect(response.body.singleResult.errors).toBeUndefined(); - const returnedData = response.body.singleResult.data; - expect(returnedData?.success).toBe(true); - expect(returnedData?.notification).toStrictEqual(notificationInput); + const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; + expect(notificationResponse?.success).toBe(true); + expect(notificationResponse?.notification).toStrictEqual(notificationInput); }); it("fails if the shuttle ID doesn't exist", async () => { @@ -73,7 +73,8 @@ describe("MutationResolvers", () => { assert(response.body.kind === "single"); expect(response.body.singleResult.errors).toBeUndefined(); - expect(response.body.singleResult.data?.success).toBe(false); + const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; + expect(notificationResponse.success).toBe(false); }); it("fails if the stop ID doesn't exist", async () => { From cea7b48323f83c895822cb2e6067135a3e6e5d2a Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:22:17 -0800 Subject: [PATCH 16/27] add mutation resolvers to merged resolvers --- src/MergedResolvers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/MergedResolvers.ts b/src/MergedResolvers.ts index bfa40eb..3c0caad 100644 --- a/src/MergedResolvers.ts +++ b/src/MergedResolvers.ts @@ -7,6 +7,7 @@ import { OrderedStopResolvers } from "./resolvers/OrderedStopResolvers"; import { StopResolvers } from "./resolvers/StopResolvers"; import { ShuttleResolvers } from "./resolvers/ShuttleResolvers"; import { RouteResolvers } from "./resolvers/RouteResolvers"; +import { MutationResolvers } from "./resolvers/MutationResolvers"; export const MergedResolvers: Resolvers = { ...QueryResolvers, @@ -16,4 +17,5 @@ export const MergedResolvers: Resolvers = { ...StopResolvers, ...OrderedStopResolvers, ...EtaResolvers, -}; \ No newline at end of file + ...MutationResolvers, +}; From 94c15f93dd096edf2f36d8157e2721595ac1289f Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:24:37 -0800 Subject: [PATCH 17/27] add same test for stop resolvers + extract apollo request to function --- test/resolvers/MutationResolverTests.test.ts | 45 ++++++++++++-------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index 72f4c9f..86ff7a6 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from "@jest/globals"; import { setupTestServerContext, setupTestServerHolder } from "../testHelpers/apolloTestServerHelpers"; -import assert = require("node:assert"); import { addMockShuttleToRepository, addMockStopToRepository, addMockSystemToRepository } from "../testHelpers/repositorySetupHelpers"; +import assert = require("node:assert"); describe("MutationResolvers", () => { const holder = setupTestServerHolder() @@ -26,6 +26,18 @@ describe("MutationResolvers", () => { } ` + async function getServerResponse(notificationInput: { deviceId: string; shuttleId: string; stopId: string }) { + return await holder.testServer.executeOperation({ + query, + variables: { + input: notificationInput, + } + }, { + contextValue: context + }); + } + + it("adds a notification to the notification service", async () => { const system = await addMockSystemToRepository(context.repository); const shuttle = await addMockShuttleToRepository(context.repository, system.id); @@ -36,14 +48,7 @@ describe("MutationResolvers", () => { shuttleId: shuttle.id, stopId: stop.id, }; - const response = await holder.testServer.executeOperation({ - query, - variables: { - input: notificationInput, - } - }, { - contextValue: context, - }); + const response = await getServerResponse(notificationInput); assert(response.body.kind === "single"); expect(response.body.singleResult.errors).toBeUndefined(); @@ -62,14 +67,7 @@ describe("MutationResolvers", () => { shuttleId: "1", stopId: stop.id, } - const response = await holder.testServer.executeOperation({ - query, - variables: { - input: notificationInput, - } - }, { - contextValue: context - }); + const response = await getServerResponse(notificationInput); assert(response.body.kind === "single"); expect(response.body.singleResult.errors).toBeUndefined(); @@ -78,7 +76,20 @@ describe("MutationResolvers", () => { }); it("fails if the stop ID doesn't exist", async () => { + const system = await addMockSystemToRepository(context.repository); + const shuttle = await addMockShuttleToRepository(context.repository, system.id); + const notificationInput = { + deviceId: "1", + shuttleId: shuttle.id, + stopId: "1", + } + const response = await getServerResponse(notificationInput); + + assert(response.body.kind === "single"); + expect(response.body.singleResult.errors).toBeUndefined(); + const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; + expect(notificationResponse.success).toBe(false); }); }); From 69e2748eec49678bd6283f8be380b47e5cc23840 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:25:31 -0800 Subject: [PATCH 18/27] add additional check for notification service scheduling --- test/resolvers/MutationResolverTests.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index 86ff7a6..afad9d8 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -56,6 +56,8 @@ describe("MutationResolvers", () => { const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; expect(notificationResponse?.success).toBe(true); expect(notificationResponse?.notification).toStrictEqual(notificationInput); + + expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(true); }); it("fails if the shuttle ID doesn't exist", async () => { @@ -73,6 +75,8 @@ describe("MutationResolvers", () => { expect(response.body.singleResult.errors).toBeUndefined(); const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; expect(notificationResponse.success).toBe(false); + + expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(false); }); it("fails if the stop ID doesn't exist", async () => { @@ -90,6 +94,8 @@ describe("MutationResolvers", () => { expect(response.body.singleResult.errors).toBeUndefined(); const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; expect(notificationResponse.success).toBe(false); + + expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(false); }); }); From 1627bc39fe247f95c80df7a4eebb47e2aea696ea Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:27:34 -0800 Subject: [PATCH 19/27] move duplicate code to method --- test/resolvers/MutationResolverTests.test.ts | 25 ++++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index afad9d8..5ab147f 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -6,6 +6,7 @@ import { addMockSystemToRepository } from "../testHelpers/repositorySetupHelpers"; import assert = require("node:assert"); +import { NotificationInput } from "../../src/generated/graphql"; describe("MutationResolvers", () => { const holder = setupTestServerHolder() @@ -37,6 +38,15 @@ describe("MutationResolvers", () => { }); } + function assertFailedResponse(response: any, notificationInput: NotificationInput) { + assert(response.body.kind === "single"); + expect(response.body.singleResult.errors).toBeUndefined(); + const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; + expect(notificationResponse.success).toBe(false); + + expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(false); + } + it("adds a notification to the notification service", async () => { const system = await addMockSystemToRepository(context.repository); @@ -70,13 +80,7 @@ describe("MutationResolvers", () => { stopId: stop.id, } const response = await getServerResponse(notificationInput); - - assert(response.body.kind === "single"); - expect(response.body.singleResult.errors).toBeUndefined(); - const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; - expect(notificationResponse.success).toBe(false); - - expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(false); + assertFailedResponse(response, notificationInput); }); it("fails if the stop ID doesn't exist", async () => { @@ -90,12 +94,7 @@ describe("MutationResolvers", () => { } const response = await getServerResponse(notificationInput); - assert(response.body.kind === "single"); - expect(response.body.singleResult.errors).toBeUndefined(); - const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; - expect(notificationResponse.success).toBe(false); - - expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(false); + assertFailedResponse(response, notificationInput); }); }); From d7b15812f3e5c3402536bee919f2f6b1ccde4d57 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:30:26 -0800 Subject: [PATCH 20/27] make responses mandatory for mutations --- schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schema.graphqls b/schema.graphqls index b606eb8..6296660 100644 --- a/schema.graphqls +++ b/schema.graphqls @@ -67,8 +67,8 @@ type Query { # Mutations type Mutation { - scheduleNotification(input: NotificationInput!): NotificationResponse - cancelNotification(input: NotificationInput!): NotificationResponse + scheduleNotification(input: NotificationInput!): NotificationResponse! + cancelNotification(input: NotificationInput!): NotificationResponse! } input NotificationInput { From acc4d08716632371566782b91cc2bdeb18122841 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:32:33 -0800 Subject: [PATCH 21/27] add a test for cancelling notification --- test/resolvers/MutationResolverTests.test.ts | 65 +++++++++++++++----- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index 5ab147f..de6e190 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -12,6 +12,17 @@ describe("MutationResolvers", () => { const holder = setupTestServerHolder() const context = setupTestServerContext(); + async function getServerResponse(query: string, notificationInput: { deviceId: string; shuttleId: string; stopId: string }) { + return await holder.testServer.executeOperation({ + query, + variables: { + input: notificationInput, + } + }, { + contextValue: context + }); + } + describe("scheduleNotification", () => { const query = ` mutation ScheduleNotification($input: NotificationInput!) { @@ -27,17 +38,6 @@ describe("MutationResolvers", () => { } ` - async function getServerResponse(notificationInput: { deviceId: string; shuttleId: string; stopId: string }) { - return await holder.testServer.executeOperation({ - query, - variables: { - input: notificationInput, - } - }, { - contextValue: context - }); - } - function assertFailedResponse(response: any, notificationInput: NotificationInput) { assert(response.body.kind === "single"); expect(response.body.singleResult.errors).toBeUndefined(); @@ -58,7 +58,7 @@ describe("MutationResolvers", () => { shuttleId: shuttle.id, stopId: stop.id, }; - const response = await getServerResponse(notificationInput); + const response = await getServerResponse(query, notificationInput); assert(response.body.kind === "single"); expect(response.body.singleResult.errors).toBeUndefined(); @@ -79,7 +79,7 @@ describe("MutationResolvers", () => { shuttleId: "1", stopId: stop.id, } - const response = await getServerResponse(notificationInput); + const response = await getServerResponse(query, notificationInput); assertFailedResponse(response, notificationInput); }); @@ -92,14 +92,51 @@ describe("MutationResolvers", () => { shuttleId: shuttle.id, stopId: "1", } - const response = await getServerResponse(notificationInput); + const response = await getServerResponse(query, notificationInput); assertFailedResponse(response, notificationInput); }); }); describe("cancelNotification", () => { + const query = ` + query CancelNotification($input: NotificationInput!) { + cancelNotification(input: $input) { + success + message + data { + deviceId + shuttleId + stopId + } + } + } + ` + it("removes the notification from the notification service", async () => { + const system = await addMockSystemToRepository(context.repository); + const shuttle = await addMockShuttleToRepository(context.repository, system.id); + const stop = await addMockStopToRepository(context.repository, system.id); + + const notificationInput = { + deviceId: "1", + shuttleId: shuttle.id, + stopId: stop.id, + } + await context.notificationService.scheduleNotification(notificationInput); + + const response = await getServerResponse(query, notificationInput); + + assert(response.body.kind === "single"); + expect(response.body.singleResult.errors).toBeUndefined(); + + const cancelledNotification = response.body.singleResult.data?.cancelNotification as any; + expect(cancelledNotification).toStrictEqual(notificationInput); + + expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(false); + }); + + it("fails if the notification doesn't exist", async () => { }); }); From 71698da1736d25ecdb8e05da12752487f89acf32 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:33:48 -0800 Subject: [PATCH 22/27] fix assertion for nested graphql response --- test/resolvers/MutationResolverTests.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index de6e190..2910586 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -100,7 +100,7 @@ describe("MutationResolvers", () => { describe("cancelNotification", () => { const query = ` - query CancelNotification($input: NotificationInput!) { + mutation CancelNotification($input: NotificationInput!) { cancelNotification(input: $input) { success message @@ -130,8 +130,9 @@ describe("MutationResolvers", () => { assert(response.body.kind === "single"); expect(response.body.singleResult.errors).toBeUndefined(); - const cancelledNotification = response.body.singleResult.data?.cancelNotification as any; - expect(cancelledNotification).toStrictEqual(notificationInput); + const notificationResponse = response.body.singleResult.data?.cancelNotification as any; + expect(notificationResponse.success).toBe(true); + expect(notificationResponse.data).toStrictEqual(notificationInput); expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(false); }); From e667e895560d24b18159e27988bcbe22db22a457 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:35:24 -0800 Subject: [PATCH 23/27] add test for if notification doesn't exist on repository --- test/resolvers/MutationResolverTests.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index 2910586..c5065ca 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -138,7 +138,19 @@ describe("MutationResolvers", () => { }); it("fails if the notification doesn't exist", async () => { + const notificationInput = { + deviceId: "1", + shuttleId: "1", + stopId: "1", + } + const response = await getServerResponse(query, notificationInput); + + assert(response.body.kind === "single"); + expect(response.body.singleResult.errors).toBeUndefined(); + + const notificationResponse = response.body.singleResult.data?.cancelNotification as any; + expect(notificationResponse.success).toBe(false); }); }); }); From fa40d15f5a2771b2bef569722619eece17174b3d Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:40:02 -0800 Subject: [PATCH 24/27] change toStrictEqual to toEqual for notification assertion --- test/resolvers/MutationResolverTests.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index c5065ca..2c84cce 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -65,7 +65,7 @@ describe("MutationResolvers", () => { const notificationResponse = response.body.singleResult.data?.scheduleNotification as any; expect(notificationResponse?.success).toBe(true); - expect(notificationResponse?.notification).toStrictEqual(notificationInput); + expect(notificationResponse?.data).toEqual(notificationInput); expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(true); }); From 2b0bef876ca2cca09b518c00cec6c4a46272a506 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:40:09 -0800 Subject: [PATCH 25/27] implement scheduleNotification --- src/resolvers/MutationResolvers.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/resolvers/MutationResolvers.ts b/src/resolvers/MutationResolvers.ts index 9332e8b..18cb3d2 100644 --- a/src/resolvers/MutationResolvers.ts +++ b/src/resolvers/MutationResolvers.ts @@ -4,9 +4,27 @@ import { ServerContext } from "../ServerContext"; export const MutationResolvers: Resolvers = { Mutation: { scheduleNotification: async (_parent, args, context, _info) => { + const shuttle = await context.repository.getShuttleById(args.input.shuttleId); + if (!shuttle) { + return { + message: "Shuttle ID doesn't exist", + success: false, + } + } + const stop = await context.repository.getStopById(args.input.stopId); + if (!stop) { + return { + message: "Stop ID doesn't exist", + success: false, + } + } + + await context.notificationService.scheduleNotification(args.input); + const response: NotificationResponse = { - message: "Not implemented", - success: false + message: "Notification scheduled", + success: true, + data: args.input, } return response; }, From 4c4bd0fc4aed7c3aba48ac578ee705fbe15ea024 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:41:57 -0800 Subject: [PATCH 26/27] swap toStrictEqual to toEqual for cancelNotification test --- test/resolvers/MutationResolverTests.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resolvers/MutationResolverTests.test.ts b/test/resolvers/MutationResolverTests.test.ts index 2c84cce..d4f5318 100644 --- a/test/resolvers/MutationResolverTests.test.ts +++ b/test/resolvers/MutationResolverTests.test.ts @@ -132,7 +132,7 @@ describe("MutationResolvers", () => { const notificationResponse = response.body.singleResult.data?.cancelNotification as any; expect(notificationResponse.success).toBe(true); - expect(notificationResponse.data).toStrictEqual(notificationInput); + expect(notificationResponse.data).toEqual(notificationInput); expect(context.notificationService.isNotificationScheduled(notificationInput)).toBe(false); }); From d3c824be68063fd60844f73b44af8a96efd735bb Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 4 Feb 2025 11:42:02 -0800 Subject: [PATCH 27/27] implement cancelNotification --- src/resolvers/MutationResolvers.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/resolvers/MutationResolvers.ts b/src/resolvers/MutationResolvers.ts index 18cb3d2..c1a4ce3 100644 --- a/src/resolvers/MutationResolvers.ts +++ b/src/resolvers/MutationResolvers.ts @@ -29,11 +29,19 @@ export const MutationResolvers: Resolvers = { return response; }, cancelNotification: async (_parent, args, context, _info) => { - const response: NotificationResponse = { - message: "Not implemented", - success: false + if (context.notificationService.isNotificationScheduled(args.input)) { + await context.notificationService.cancelNotificationIfExists(args.input); + return { + success: true, + message: "Notification cancelled", + data: args.input, + } + } + + return { + success: false, + message: "Notification doesn't exist" } - return response; }, }, }