diff --git a/cdsp/information-layer/handlers/src/HandlerBase.ts b/cdsp/information-layer/handlers/src/HandlerBase.ts index 92d14cc..ead01b1 100644 --- a/cdsp/information-layer/handlers/src/HandlerBase.ts +++ b/cdsp/information-layer/handlers/src/HandlerBase.ts @@ -100,13 +100,13 @@ export abstract class HandlerBase { } /** - * Generic function to create or remove a subscription message. + * Generic function to create a subscription status message. * @param type - Type of subscription message. * @param message - The original message from client. * @param status - The status of the subscription. * @returns - The transformed message. */ - protected createSubscribeMessage( + protected createSubscribeStatusMessage( type: "subscribe" | "unsubscribe", message: Pick, status: string diff --git a/cdsp/information-layer/handlers/src/iotdb/src/IoTDBHandler.ts b/cdsp/information-layer/handlers/src/iotdb/src/IoTDBHandler.ts index 4f271e8..aaf3e4e 100644 --- a/cdsp/information-layer/handlers/src/iotdb/src/IoTDBHandler.ts +++ b/cdsp/information-layer/handlers/src/iotdb/src/IoTDBHandler.ts @@ -42,7 +42,7 @@ export class IoTDBHandler extends HandlerBase { throw new Error("Invalid database configuration."); } this.session = new Session(); - this.subscriptionSimulator = new SubscriptionSimulator(this.session, this.sendMessageToClient, this.createUpdateMessage); + this.subscriptionSimulator = new SubscriptionSimulator(this.session, this.sendMessageToClient, this.createUpdateMessage, this.createSubscribeStatusMessage); } async authenticateAndConnect( diff --git a/cdsp/information-layer/handlers/src/iotdb/src/SubscriptionSimulator.test.ts b/cdsp/information-layer/handlers/src/iotdb/src/SubscriptionSimulator.test.ts index 69a9b07..fe35f92 100644 --- a/cdsp/information-layer/handlers/src/iotdb/src/SubscriptionSimulator.test.ts +++ b/cdsp/information-layer/handlers/src/iotdb/src/SubscriptionSimulator.test.ts @@ -12,7 +12,7 @@ jest.mock("../config/database-params", () => ({ })); import { SubscriptionSimulator } from './SubscriptionSimulator'; -import { WebSocketWithId, Message } from '../../../utils/data-types'; +import { WebSocketWithId, Message, MessageBase } from '../../../utils/data-types'; import { Session } from './Session'; import { SessionDataSet } from "../utils/SessionDataSet"; import { transformSessionDataSet } from "../utils/database-helper"; @@ -35,8 +35,19 @@ describe('SubscriptionSimulator', () => { beforeEach(() => { mockSession = new Session(); + const createSubscribeStatusMessageMock = jest.fn< + MessageBase, + ["subscribe" | "unsubscribe", Pick, string] + >((type, message, status) => ({ + type, // Properly narrowed type + id: message.id, + tree: message.tree, + uuid: message.uuid, + status, + })); + sendMessageToClientMock = jest.fn(); - simulator = new SubscriptionSimulator(mockSession, sendMessageToClientMock, jest.fn()); + simulator = new SubscriptionSimulator(mockSession, sendMessageToClientMock, jest.fn(), createSubscribeStatusMessageMock); mockWebSocket = { id: 'ws1' } as WebSocketWithId; mockMessage = { id: 'vehicle123', tree: 'VSS' } as Message; @@ -56,6 +67,28 @@ describe('SubscriptionSimulator', () => { * subscribe */ + test('calls sendMessageToClient if subscription was successful', () => { + + // Act: Call subscribe with a new WebSocket and message key + simulator.subscribe(mockMessage, mockWebSocket); + + // Assert: Verify sendMessageToClient was called + expect(sendMessageToClientMock).toHaveBeenCalled(); + + // Capture the actual message passed to sendMessageToClient + const [webSocketArg, messageArg] = sendMessageToClientMock.mock.calls[0]; + + // Check the WebSocket argument + expect(webSocketArg).toBe(mockWebSocket); + + // Check the message content + expect(messageArg).toEqual( + expect.objectContaining({ + status: "succeed", + tree: "VSS", + }) + ); + }); test('calls sendMessageToClient if subscription with same WebSocket already exists', () => { // Arrange: Manually add a subscription to simulate the "already exists" condition @@ -116,6 +149,31 @@ describe('SubscriptionSimulator', () => { /* * unsubscribe */ + test('calls sendMessageToClient if unsubscribe was successful', () => { + + // Arrange: Add a subscription with same key and same websocket + simulator['subscriptions'].set(mockKey, [mockWebSocket]); + + // Act: Call subscribe with a new WebSocket and message key + simulator.unsubscribe(mockMessage, mockWebSocket); + + // Assert: Verify sendMessageToClient was called + expect(sendMessageToClientMock).toHaveBeenCalled(); + + // Capture the actual message passed to sendMessageToClient + const [webSocketArg, messageArg] = sendMessageToClientMock.mock.calls[0]; + + // Check the WebSocket argument + expect(webSocketArg).toBe(mockWebSocket); + + // Check the message content + expect(messageArg).toEqual( + expect.objectContaining({ + status: "succeed", + tree: "VSS", + }) + ); + }); test('remove websocket from existing subscription if websocket and subscription exist', () => { // Arrange: Add a subscription with same key and same websocket + 1 other websocket diff --git a/cdsp/information-layer/handlers/src/iotdb/src/SubscriptionSimulator.ts b/cdsp/information-layer/handlers/src/iotdb/src/SubscriptionSimulator.ts index deab037..d34f838 100644 --- a/cdsp/information-layer/handlers/src/iotdb/src/SubscriptionSimulator.ts +++ b/cdsp/information-layer/handlers/src/iotdb/src/SubscriptionSimulator.ts @@ -18,15 +18,18 @@ export class SubscriptionSimulator { private subscriptions: SubscriptionMap = new Map(); private sendMessageToClient: ( ws: WebSocketWithId, message: Message | MessageBase | ErrorMessage) => void; private createUpdateMessage: (id: string, tree: string, uuid: string, nodes: Array<{ name: string; value: any }>) => Message; + private createSubscribeStatusMessage: (type: "subscribe" | "unsubscribe", message: Pick, status: string) => MessageBase; constructor(session: Session, sendMessageToClient: (ws: WebSocketWithId, message: Message | MessageBase | ErrorMessage) => void, - createUpdateMessage: (id: string, tree: string, uuid: string, nodes: Array<{ name: string; value: any }>) => Message) + createUpdateMessage: (id: string, tree: string, uuid: string, nodes: Array<{ name: string; value: any }>) => Message, + createSubscribeStatusMessage: (type: "subscribe" | "unsubscribe", message: Pick, status: string) => MessageBase) { this.session = session; this.notifyDatabaseChanges = this.notifyDatabaseChanges.bind(this); this.sendMessageToClient = sendMessageToClient; this.createUpdateMessage = createUpdateMessage; + this.createSubscribeStatusMessage = createSubscribeStatusMessage; } /** @@ -66,6 +69,12 @@ export class SubscriptionSimulator { this.timeIntervalLowerLimit = Date.now(); logMessage("started timer"); } + + this.sendMessageToClient( + wsOfNewSubscription, + this.createSubscribeStatusMessage("subscribe", message, "succeed") + ); + logMessage("subscribed"); this.logSubscriptions(); } @@ -104,6 +113,12 @@ export class SubscriptionSimulator { } this.removeTimerIfNoSubscription(); + + this.sendMessageToClient( + wsOfSubscriptionToBeDeleted, + this.createSubscribeStatusMessage("unsubscribe", message, "succeed") + ); + logMessage("unsubscribed"); this.logSubscriptions(); } diff --git a/cdsp/information-layer/handlers/src/realmdb/src/RealmDbHandler.ts b/cdsp/information-layer/handlers/src/realmdb/src/RealmDbHandler.ts index 2d103f4..dd26126 100644 --- a/cdsp/information-layer/handlers/src/realmdb/src/RealmDbHandler.ts +++ b/cdsp/information-layer/handlers/src/realmdb/src/RealmDbHandler.ts @@ -203,7 +203,7 @@ export class RealmDBHandler extends HandlerBase { this.sendMessageToClient( ws, - this.createSubscribeMessage("subscribe", message, "succeed") + this.createSubscribeStatusMessage("subscribe", message, "succeed") ); logWithColor( @@ -275,7 +275,7 @@ export class RealmDBHandler extends HandlerBase { this.sendMessageToClient( ws, - this.createSubscribeMessage("unsubscribe", message, "succeed") + this.createSubscribeStatusMessage("unsubscribe", message, "succeed") ); } else { this.sendMessageToClient(