From a57f5aa1cef9be002239d2487d9f8d7e0ea5739a Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 12 Jun 2026 13:46:58 +0200 Subject: [PATCH] test(coding-agent): expanded tests for model scope notification and collab link parsing - Extended `parseCollabLink` test coverage to reject 16-byte and 40-byte fragments instead of only rejecting short keys. - Added coverage for full-link fragments to ensure key and write token are parsed when present. - Updated `GuestClient` test setup so `welcomeFrame` carries a `readOnly` flag into snapshot assertions. --- packages/coding-agent/src/main.ts | 4 +--- .../coding-agent/test/collab/crypto.test.ts | 8 +++++++- .../test/main-model-scope-notification.test.ts | 15 ++++++++++++--- packages/collab-web/package.json | 2 +- packages/collab-web/test/client.test.ts | 6 +++--- packages/collab-web/test/link.test.ts | 17 ++++++++++++++++- 6 files changed, 40 insertions(+), 12 deletions(-) diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 5b2608af9..c22ffa2e7 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -254,9 +254,7 @@ export function buildModelScopeNotification( const modelList = scopedModelsForDisplay .map(scopedModel => { const thinkingStr = - scopedModel.explicitThinkingLevel && scopedModel.thinkingLevel - ? `:${scopedModel.thinkingLevel}` - : ""; + scopedModel.explicitThinkingLevel && scopedModel.thinkingLevel ? `:${scopedModel.thinkingLevel}` : ""; return `${scopedModel.model.id}${thinkingStr}`; }) .join(", "); diff --git a/packages/coding-agent/test/collab/crypto.test.ts b/packages/coding-agent/test/collab/crypto.test.ts index 8c261d975..706330a3b 100644 --- a/packages/coding-agent/test/collab/crypto.test.ts +++ b/packages/coding-agent/test/collab/crypto.test.ts @@ -1,5 +1,11 @@ import { describe, expect, it } from "bun:test"; -import { generateRoomKey, generateWriteToken, importRoomKey, open, seal } from "@oh-my-pi/pi-coding-agent/collab/crypto"; +import { + generateRoomKey, + generateWriteToken, + importRoomKey, + open, + seal, +} from "@oh-my-pi/pi-coding-agent/collab/crypto"; import { type CollabFrame, DEFAULT_RELAY_URL, diff --git a/packages/coding-agent/test/main-model-scope-notification.test.ts b/packages/coding-agent/test/main-model-scope-notification.test.ts index 74ac039f8..54282155d 100644 --- a/packages/coding-agent/test/main-model-scope-notification.test.ts +++ b/packages/coding-agent/test/main-model-scope-notification.test.ts @@ -1,8 +1,9 @@ import { describe, expect, it } from "bun:test"; +import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { buildModel } from "@oh-my-pi/pi-catalog/build"; import type { ScopedModel } from "@oh-my-pi/pi-coding-agent/config/model-resolver"; -import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { buildModelScopeNotification } from "@oh-my-pi/pi-coding-agent/main"; + function scopedModel(id: string): ScopedModel { return { model: buildModel({ @@ -33,7 +34,11 @@ describe("buildModelScopeNotification", () => { }); }); it("includes thinking suffix only when explicitly scoped", () => { - const withExplicit = { ...scopedModel("claude-sonnet-4-5"), thinkingLevel: "high" as ThinkingLevel, explicitThinkingLevel: true }; + const withExplicit = { + ...scopedModel("claude-sonnet-4-5"), + thinkingLevel: "high" as ThinkingLevel, + explicitThinkingLevel: true, + }; expect(buildModelScopeNotification([withExplicit], false)).toEqual({ kind: "info", message: "Model scope: claude-sonnet-4-5:high (Ctrl+P to cycle)", @@ -44,7 +49,11 @@ describe("buildModelScopeNotification", () => { // `applyRootSessionOptions` fills `sessionOptions.scopedModels[*].thinkingLevel` // with the global default for Ctrl+P cycling — the banner must not surface that // default as if the user had scoped `:high`. - const withDefault = { ...scopedModel("claude-sonnet-4-5"), thinkingLevel: "high" as ThinkingLevel, explicitThinkingLevel: false }; + const withDefault = { + ...scopedModel("claude-sonnet-4-5"), + thinkingLevel: "high" as ThinkingLevel, + explicitThinkingLevel: false, + }; expect(buildModelScopeNotification([withDefault], false)).toEqual({ kind: "info", message: "Model scope: claude-sonnet-4-5 (Ctrl+P to cycle)", diff --git a/packages/collab-web/package.json b/packages/collab-web/package.json index 392b6926f..95a4fd10f 100644 --- a/packages/collab-web/package.json +++ b/packages/collab-web/package.json @@ -26,7 +26,7 @@ "dev": "bun ./index.html", "mock-host": "bun scripts/mock-host.ts", "relay": "bun scripts/local-relay.ts", - "build": "rm -rf dist && bun build ./index.html --outdir=dist --minify --asset-naming=[name].[ext] && cp -R public/. dist/", + "build": "rm -rf dist && bun build ./index.html --outdir=dist --minify --entry-naming=[hash].[ext] --chunk-naming=[hash].[ext] --asset-naming=[hash].[ext] && mv dist/*.html dist/index.html && cp -R public/. dist/", "prepack": "bun run build", "test": "bun test --parallel", "check": "biome check . && bun run check:types", diff --git a/packages/collab-web/test/client.test.ts b/packages/collab-web/test/client.test.ts index e94042d4d..3bee1f5cc 100644 --- a/packages/collab-web/test/client.test.ts +++ b/packages/collab-web/test/client.test.ts @@ -50,8 +50,8 @@ function messageEntry(id: string, message: WireMessage): SessionEntry { return { type: "message", id, parentId: null, timestamp: "2026-06-12T00:00:01Z", message }; } -function welcomeFrame(entries: SessionEntry[] = []): HostFrame { - return { t: "welcome", proto: 1, header: HEADER, entries, state: STATE, agents: AGENTS }; +function welcomeFrame(entries: SessionEntry[] = [], readOnly?: boolean): HostFrame { + return { t: "welcome", proto: 1, header: HEADER, entries, state: STATE, agents: AGENTS, readOnly }; } function liveClient(entries: SessionEntry[] = []): GuestClient { @@ -82,7 +82,7 @@ describe("GuestClient frame apply", () => { it("welcome readOnly flag lands in the snapshot", () => { const client = new GuestClient(LINK, "tester"); expect(client.getSnapshot().readOnly).toBe(false); - client.applyFrameForTest({ ...welcomeFrame(), readOnly: true } as HostFrame); + client.applyFrameForTest(welcomeFrame([], true)); expect(client.getSnapshot().readOnly).toBe(true); }); diff --git a/packages/collab-web/test/link.test.ts b/packages/collab-web/test/link.test.ts index 022ec19da..9a2e0ca76 100644 --- a/packages/collab-web/test/link.test.ts +++ b/packages/collab-web/test/link.test.ts @@ -39,9 +39,24 @@ describe("collab link parsing", () => { expect("error" in parseCollabLink(`ws://relay.example.com/r/${ROOM}#${KEY_TEXT}`)).toBe(true); }); - it("rejects keys that are not 32 base64url bytes", () => { + it("rejects keys that are not 32 or 48 base64url bytes", () => { const shortKey = encodeBase64Url(new Uint8Array(16)); expect("error" in parseCollabLink(`${ROOM}#${shortKey}`)).toBe(true); + const midKey = encodeBase64Url(new Uint8Array(40)); + expect("error" in parseCollabLink(`${ROOM}#${midKey}`)).toBe(true); + }); + + it("splits full-link fragments into key and write token", () => { + const token = Uint8Array.from({ length: 16 }, (_, i) => 0xf0 + i); + const full = parseCollabLink(formatCollabLink(DEFAULT_RELAY_URL, ROOM, KEY, token)); + if ("error" in full) throw new Error(full.error); + expect(full.key).toEqual(KEY); + expect(full.writeToken).toEqual(token); + + const view = parseCollabLink(formatCollabLink(DEFAULT_RELAY_URL, ROOM, KEY)); + if ("error" in view) throw new Error(view.error); + expect(view.key).toEqual(KEY); + expect(view.writeToken).toBeUndefined(); }); it("parses web deep links (https:///#)", () => {