5ba7717ecd
Critical Fix: - StateStore now uses global Hooks.callAll directly (per spec) - Removed hooks parameter from StateStore constructor - Updated module.js to pass only adapter.settings - Updated tests to stub globalThis.Hooks Minor Cleanup: - Fixed misleading warning in SocketHandler.registerPendingOp - Added clarifying comment for setMatrix _revision behavior Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
120 lines
3.9 KiB
JavaScript
120 lines
3.9 KiB
JavaScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
createSocketIntentMessage,
|
|
createSocketEchoMessage,
|
|
isValidSocketMessage,
|
|
SOCKET_EVENTS,
|
|
MAX_PAYLOAD_BYTES,
|
|
} from "../../../src/contracts/socket-message.js";
|
|
import { SOCKET_PAYLOADS } from "../../fixtures/socket-payloads.js";
|
|
|
|
describe("socket-message contract", () => {
|
|
describe("createSocketIntentMessage()", () => {
|
|
it("creates a valid intent message", () => {
|
|
const msg = createSocketIntentMessage("op-1", "user-1", "hidden", 0);
|
|
expect(msg.event).toBe(SOCKET_EVENTS.VISIBILITY_SET);
|
|
const p = /** @type {any} */ (msg.payload);
|
|
expect(p.opId).toBe("op-1");
|
|
expect(p.userId).toBe("user-1");
|
|
expect(p.targetState).toBe("hidden");
|
|
expect(p.baseRevision).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("createSocketEchoMessage()", () => {
|
|
it("creates a valid echo message", () => {
|
|
const msg = createSocketEchoMessage("op-1", "user-1", "hidden", 1);
|
|
expect(msg.event).toBe(SOCKET_EVENTS.VISIBILITY_UPDATED);
|
|
const p = /** @type {any} */ (msg.payload);
|
|
expect(p.opId).toBe("op-1");
|
|
expect(p.state).toBe("hidden");
|
|
expect(p.revision).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("isValidSocketMessage()", () => {
|
|
it("accepts a valid intent from fixture", () => {
|
|
expect(() => isValidSocketMessage(SOCKET_PAYLOADS.validIntent)).not.toThrow();
|
|
});
|
|
|
|
it("accepts a valid echo from fixture", () => {
|
|
expect(() => isValidSocketMessage(SOCKET_PAYLOADS.validEcho)).not.toThrow();
|
|
});
|
|
|
|
it("throws on missing opId", () => {
|
|
expect(() => isValidSocketMessage(SOCKET_PAYLOADS.missingOpId)).toThrow(TypeError);
|
|
});
|
|
|
|
it("throws on unknown event", () => {
|
|
expect(() => isValidSocketMessage(SOCKET_PAYLOADS.unknownEvent)).toThrow(TypeError);
|
|
});
|
|
|
|
it("throws on extra payload keys (intent)", () => {
|
|
expect(() => isValidSocketMessage(SOCKET_PAYLOADS.extraKeys)).toThrow(TypeError);
|
|
});
|
|
|
|
it("throws if not an object", () => {
|
|
expect(() => isValidSocketMessage(null)).toThrow(TypeError);
|
|
expect(() => isValidSocketMessage("string")).toThrow(TypeError);
|
|
});
|
|
|
|
it("throws on unknown top-level keys", () => {
|
|
expect(() =>
|
|
isValidSocketMessage({ event: SOCKET_EVENTS.VISIBILITY_SET, payload: { opId: "x", userId: "y", targetState: "active", baseRevision: 0 }, extra: true })
|
|
).toThrow(TypeError);
|
|
});
|
|
|
|
it("throws on missing baseRevision in intent", () => {
|
|
expect(() => isValidSocketMessage(SOCKET_PAYLOADS.missingBaseRevision)).toThrow(TypeError);
|
|
});
|
|
|
|
it("throws on empty event string", () => {
|
|
expect(() =>
|
|
isValidSocketMessage({ event: "", payload: {} })
|
|
).toThrow(TypeError);
|
|
});
|
|
|
|
it("throws on non-finite revision in echo", () => {
|
|
expect(() =>
|
|
isValidSocketMessage({
|
|
event: SOCKET_EVENTS.VISIBILITY_UPDATED,
|
|
payload: { opId: "op-1", userId: "u-1", state: "active", revision: NaN },
|
|
})
|
|
).toThrow(TypeError);
|
|
});
|
|
|
|
it("throws on negative revision in echo", () => {
|
|
expect(() =>
|
|
isValidSocketMessage({
|
|
event: SOCKET_EVENTS.VISIBILITY_UPDATED,
|
|
payload: { opId: "op-1", userId: "u-1", state: "active", revision: -1 },
|
|
})
|
|
).toThrow(TypeError);
|
|
});
|
|
|
|
it("throws on missing revision in echo", () => {
|
|
expect(() =>
|
|
isValidSocketMessage(SOCKET_PAYLOADS.missingRevision)
|
|
).toThrow(TypeError);
|
|
});
|
|
});
|
|
|
|
describe("SOCKET_EVENTS", () => {
|
|
it("is frozen", () => {
|
|
expect(Object.isFrozen(SOCKET_EVENTS)).toBe(true);
|
|
});
|
|
|
|
it("uses scrying-pool. prefix for all events", () => {
|
|
for (const event of Object.values(SOCKET_EVENTS)) {
|
|
expect(event.startsWith("scrying-pool.")).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("MAX_PAYLOAD_BYTES", () => {
|
|
it("is 4096", () => {
|
|
expect(MAX_PAYLOAD_BYTES).toBe(4096);
|
|
});
|
|
});
|
|
});
|