From 42e51e537f00ada8d5719cae4d94b7bb5d55a0ee Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 19 Aug 2026 03:57:35 -0700 Subject: [PATCH] test(gateway): split session catalog share routes --- ...ession-catalog-share-route.test-support.ts | 12 -- .../session-catalog-share-route.test.ts | 108 ++++++++++++++++++ .../server-methods/session-catalog.test.ts | 41 ------- 3 files changed, 108 insertions(+), 53 deletions(-) delete mode 100644 src/gateway/server-methods/session-catalog-share-route.test-support.ts create mode 100644 src/gateway/server-methods/session-catalog-share-route.test.ts diff --git a/src/gateway/server-methods/session-catalog-share-route.test-support.ts b/src/gateway/server-methods/session-catalog-share-route.test-support.ts deleted file mode 100644 index 806b15d7867a..000000000000 --- a/src/gateway/server-methods/session-catalog-share-route.test-support.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { SessionCatalogProvider } from "../../plugins/session-catalog.js"; - -export const TEST_SESSION_CATALOG_SHARE_ROUTE = { - kind: "thread-id-prefix", - routeSegment: "shared-sessions", - hostId: "gateway", - identifierAlphabet: "lowercase-hex", - fullLength: 32, - minPrefixLength: 12, - lookup: "catalog-list-search-by-thread-id-prefix", - ambiguity: "multiple-results-or-next-cursor", -} as const satisfies NonNullable; diff --git a/src/gateway/server-methods/session-catalog-share-route.test.ts b/src/gateway/server-methods/session-catalog-share-route.test.ts new file mode 100644 index 000000000000..e9069432e3de --- /dev/null +++ b/src/gateway/server-methods/session-catalog-share-route.test.ts @@ -0,0 +1,108 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { createEmptyPluginRegistry } from "../../plugins/registry-empty.js"; +import type { PluginRegistry } from "../../plugins/registry-types.js"; +import type { SessionCatalogProvider } from "../../plugins/session-catalog.js"; + +type TestPluginRegistry = Omit & { + sessionCatalogs: Array<{ provider: SessionCatalogProvider }>; +}; + +const SHARE_ROUTE = { + kind: "thread-id-prefix", + routeSegment: "shared-sessions", + hostId: "gateway", + identifierAlphabet: "lowercase-hex", + fullLength: 32, + minPrefixLength: 12, + lookup: "catalog-list-search-by-thread-id-prefix", + ambiguity: "multiple-results-or-next-cursor", +} as const satisfies NonNullable; + +const hoisted = vi.hoisted(() => ({ + activeRegistry: {} as TestPluginRegistry, + hasMultipleSessionSharingIdentities: vi.fn(() => false), + listSessionEntriesReadOnly: vi.fn(() => []), +})); + +vi.mock("../../plugins/runtime.js", () => ({ + getActivePluginRegistry: () => hoisted.activeRegistry, + requireActivePluginRegistry: () => hoisted.activeRegistry, +})); +vi.mock("../../config/sessions/session-accessor.js", async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, listSessionEntriesReadOnly: hoisted.listSessionEntriesReadOnly }; +}); +vi.mock("../../state/user-profiles.js", () => ({ + hasMultipleSessionSharingIdentities: hoisted.hasMultipleSessionSharingIdentities, +})); + +const { sessionCatalogHandlers } = await import("./session-catalog.js"); + +function provider( + id: string, + overrides: Partial = {}, +): SessionCatalogProvider { + return { + id, + label: id.toUpperCase(), + list: vi.fn(async () => []), + read: vi.fn(async ({ hostId, threadId }) => ({ hostId, threadId, items: [] })), + ...overrides, + }; +} + +async function listCatalogs(params: unknown = {}) { + const respond = vi.fn(); + await sessionCatalogHandlers["sessions.catalog.list"]?.({ + params, + respond, + context: { getRuntimeConfig: () => ({}) }, + } as never); + return respond; +} + +describe("session catalog share routes", () => { + beforeEach(() => { + hoisted.activeRegistry = createEmptyPluginRegistry() as TestPluginRegistry; + hoisted.hasMultipleSessionSharingIdentities.mockReset().mockReturnValue(false); + hoisted.listSessionEntriesReadOnly.mockReset().mockReturnValue([]); + }); + + it("projects uniquely owned routes and suppresses collisions", async () => { + hoisted.activeRegistry.sessionCatalogs.push({ + provider: provider("external", { shareRoute: SHARE_ROUTE }), + }); + let catalogs = ( + (await listCatalogs({ catalogId: "external" })).mock.calls[0]![1] as { + catalogs: Array<{ shareRoute?: unknown }>; + } + ).catalogs; + expect(catalogs[0]?.shareRoute).toEqual(SHARE_ROUTE); + + hoisted.activeRegistry.sessionCatalogs = [ + { provider: provider("first", { shareRoute: SHARE_ROUTE }) }, + { provider: provider("second", { shareRoute: SHARE_ROUTE }) }, + ]; + catalogs = ((await listCatalogs()).mock.calls[0]![1] as { catalogs: typeof catalogs }).catalogs; + expect(catalogs.every((catalog) => catalog.shareRoute === undefined)).toBe(true); + }); + + it.each(["chat", "plugin", "settings"])( + "does not project the reserved %s route", + async (routeSegment) => { + hoisted.activeRegistry.sessionCatalogs = [ + { + provider: provider("external", { + shareRoute: { ...SHARE_ROUTE, routeSegment }, + }), + }, + ]; + + const respond = await listCatalogs({ catalogId: "external" }); + + expect(respond).toHaveBeenCalledWith(true, { + catalogs: [expect.not.objectContaining({ shareRoute: expect.anything() })], + }); + }, + ); +}); diff --git a/src/gateway/server-methods/session-catalog.test.ts b/src/gateway/server-methods/session-catalog.test.ts index 70c43d359f08..2c4205298928 100644 --- a/src/gateway/server-methods/session-catalog.test.ts +++ b/src/gateway/server-methods/session-catalog.test.ts @@ -5,7 +5,6 @@ import { bindPluginRegistryRuntime } from "../../plugins/registry-runtime-bindin import type { PluginRegistry } from "../../plugins/registry-types.js"; import { createPluginRuntime } from "../../plugins/runtime/index.js"; import type { SessionCatalogProvider } from "../../plugins/session-catalog.js"; -import { TEST_SESSION_CATALOG_SHARE_ROUTE as SHARE_ROUTE } from "./session-catalog-share-route.test-support.js"; type TestPluginRegistry = Omit & { sessionCatalogs: Array<{ @@ -143,46 +142,6 @@ describe("session catalog Gateway methods", () => { }); }); - it("projects uniquely owned share routes and suppresses collisions", async () => { - hoisted.activeRegistry.sessionCatalogs.push({ - provider: provider("external", { shareRoute: SHARE_ROUTE }), - }); - let catalogs = ( - (await call("sessions.catalog.list", { catalogId: "external" })).mock.calls[0]![1] as { - catalogs: Array<{ shareRoute?: unknown }>; - } - ).catalogs; - expect(catalogs[0]?.shareRoute).toEqual(SHARE_ROUTE); - - hoisted.activeRegistry.sessionCatalogs = [ - { provider: provider("first", { shareRoute: SHARE_ROUTE }) }, - { provider: provider("second", { shareRoute: SHARE_ROUTE }) }, - ]; - catalogs = ( - (await call("sessions.catalog.list", {})).mock.calls[0]![1] as { catalogs: typeof catalogs } - ).catalogs; - expect(catalogs.every((catalog) => catalog.shareRoute === undefined)).toBe(true); - }); - - it.each(["chat", "plugin", "settings"])( - "does not project the reserved %s share route", - async (routeSegment) => { - hoisted.activeRegistry.sessionCatalogs = [ - { - provider: provider("external", { - shareRoute: { ...SHARE_ROUTE, routeSegment }, - }), - }, - ]; - - const respond = await call("sessions.catalog.list", { catalogId: "external" }); - - expect(respond).toHaveBeenCalledWith(true, { - catalogs: [expect.not.objectContaining({ shareRoute: expect.anything() })], - }); - }, - ); - it("streams completed hosts to only the requesting connection", async () => { const broadcastToConnIds = vi.fn(); const host = {