diff --git a/packages/framework/presence/src/test/presenceDatastoreManager.spec.ts b/packages/framework/presence/src/test/presenceDatastoreManager.spec.ts index 7c1d0d1cfa9e..043e1bee8339 100644 --- a/packages/framework/presence/src/test/presenceDatastoreManager.spec.ts +++ b/packages/framework/presence/src/test/presenceDatastoreManager.spec.ts @@ -5,12 +5,18 @@ import { strict as assert } from "node:assert"; +import type { + InboundExtensionMessage, + RawInboundExtensionMessage, +} from "@fluidframework/container-runtime-definitions/internal"; +import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; import { EventAndErrorTrackingLogger } from "@fluidframework/test-utils/internal"; import type { SinonFakeTimers } from "sinon"; import { useFakeTimers, spy } from "sinon"; import type { AttendeeId } from "../presence.js"; import { createPresenceManager } from "../presenceManager.js"; +import type { SignalMessages } from "../protocol.js"; import type { SystemWorkspaceDatastore } from "../systemWorkspace.js"; import { MockEphemeralRuntime } from "./mockEphemeralRuntime.js"; @@ -472,5 +478,102 @@ describe("Presence", () => { assert.strictEqual(listener.callCount, 1); }); }); + + describe("receiving unrecognized message", () => { + let presence: ReturnType; + + const systemWorkspaceUpdate = { + "clientToSessionId": { + "client1": { + "rev": 0, + "timestamp": 0, + "value": attendeeId1, + }, + }, + }; + + const statesWorkspaceUpdate = { + "latest": { + [attendeeId1]: { + "rev": 1, + "timestamp": 0, + "value": {}, + }, + }, + }; + + beforeEach(() => { + presence = prepareConnectedPresence( + runtime, + attendeeId2, + connectionId2, + clock, + logger, + ); + + // Pass a little time (to mimic reality) + clock.tick(10); + }); + + /** + * Use to pretend any general inbound message is an unverified Presence message + */ + function markUnverifiedIncomingMessage( + message: InboundExtensionMessage, + ): RawInboundExtensionMessage { + return message as RawInboundExtensionMessage; + } + + function processUnrecognizedMessage({ optional }: { optional: boolean }): void { + // Mocked to look very much like a Presence message, but with an unrecognized type. + const unrecognizedMessage = { + type: "Pres: Unrecognized Message", + content: { + sendTimestamp: clock.now - 10, + avgLatency: 20, + data: { + "system:presence": systemWorkspaceUpdate, + "s:name:testStateWorkspace": statesWorkspaceUpdate, + }, + }, + clientId: "client1", + } as const satisfies Omit, "type"> & { + type: string; + }; + presence.processSignal( + optional ? ["?"] : [], + markUnverifiedIncomingMessage(unrecognizedMessage), + false, + ); + } + + it("that is NOT optional, throws", () => { + // Setup + const listener = spy(); + presence.events.on("workspaceActivated", listener); + + // Act & Verify + assert.throws( + () => processUnrecognizedMessage({ optional: false }), + (e: Error) => + validateAssertionError(e, /Unrecognized message type in critical message/), + ); + + // Verify + assert.strictEqual(listener.called, false); + }); + + it("that is optional, ignores message and does NOT throw", () => { + // Setup + const listener = spy(); + presence.events.on("workspaceActivated", listener); + + // Act & Verify + processUnrecognizedMessage({ optional: true }); + + // Verify + assert.strictEqual(listener.called, false); + }); + }); }); });