From 7a2d132c1623ae8f8f16797282b4c751055af6ea Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 31 Oct 2026 14:40:53 +0900 Subject: [PATCH] test: prepare connected chat checks for canonical and legacy Subject routes --- .../src/server/tests/chat_web_connected.rs | 2 +- .../subject_conversation_connected.ts | 54 ++++++++++++++++++- tools/web-ux/deno.json | 1 + 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/crates/workspace-server/src/server/tests/chat_web_connected.rs b/crates/workspace-server/src/server/tests/chat_web_connected.rs index c578003ba..b8c82295e 100644 --- a/crates/workspace-server/src/server/tests/chat_web_connected.rs +++ b/crates/workspace-server/src/server/tests/chat_web_connected.rs @@ -111,7 +111,7 @@ async fn chat_web_connected_busy_unknown_replay_and_authorization_use_real_runti .kill_on_drop(true) .spawn() .expect("explicit browser test requires Deno"); - let wire = serde_json::to_vec(&json!({"origin":f.url,"workspace_id":TEST_WORKSPACE_ID,"subject_path":format!("/w/{TEST_WORKSPACE_ID}/memory/{subject}"),"subject_name":"Web connected companion","conversation_id":c,"owner_token":f.owner,"foreign_token":foreign})).unwrap(); + let wire = serde_json::to_vec(&json!({"origin":f.url,"workspace_id":TEST_WORKSPACE_ID,"subject_id":subject,"subject_name":"Web connected companion","conversation_id":c,"owner_token":f.owner,"foreign_token":foreign})).unwrap(); let mut stdin = child.stdin.take().unwrap(); stdin.write_all(&wire).await.unwrap(); drop(stdin); diff --git a/tools/web-ux/browser-tests/subject_conversation_connected.ts b/tools/web-ux/browser-tests/subject_conversation_connected.ts index 1adf67f93..e728c5f4d 100644 --- a/tools/web-ux/browser-tests/subject_conversation_connected.ts +++ b/tools/web-ux/browser-tests/subject_conversation_connected.ts @@ -3,11 +3,21 @@ // deliberate lost transport acknowledgement are substituted. No live instance. import { assert, assertEquals } from "@std/assert"; import { chromium } from "playwright"; +import { + subjectChatHref, + subjectHref, +} from "../../../web/workspace/src/lib/workspace/subjektiv/routes.ts"; const input = JSON.parse(await new Response(Deno.stdin.readable).text()); const origin = new URL(input.origin); assert(origin.hostname === "127.0.0.1" && origin.protocol === "http:"); const base = `/api/w/${encodeURIComponent(input.workspace_id)}/chat/conversations`; +const detail = subjectHref(input.workspace_id, input.subject_id); +const chat = subjectChatHref(input.workspace_id, input.subject_id); +const legacy = `/w/${encodeURIComponent(input.workspace_id)}/memory/${ + encodeURIComponent(input.subject_id) +}`; +const suffix = "?from=legacy&encoded=%2Fkeep%20exact#subject-heading"; const browser = await chromium.launch({ headless: true }); try { const context = await browser.newContext({ @@ -25,10 +35,39 @@ try { `${base}/${encodeURIComponent(input.conversation_id)}/messages` ) posts.push(request.postDataJSON()); }); - const response = await page.goto(`${origin.origin}${input.subject_path}`); + const response = await page.goto(`${origin.origin}${legacy}${suffix}`); assertEquals(response?.status(), 200); - await page.getByRole("link", { name: "Open conversation" }).click(); + await page.waitForURL(`${origin.origin}${detail}${suffix}`); + const open = page.getByRole("link", { name: "Open conversation" }); + assertEquals(await open.getAttribute("href"), chat); + await open.click(); + await page.waitForURL(`${origin.origin}${chat}`); await page.getByText("No public messages yet.").waitFor(); + for (const alias of ["conversation", "chat"]) { + const response = await page.goto(`${origin.origin}${legacy}/${alias}${suffix}`); + assertEquals(response?.status(), 200); + await page.waitForURL(`${origin.origin}${chat}${suffix}`); + await page.getByText("No public messages yet.").waitFor(); + assertEquals( + await page.getByRole("link", { name: "← Subject details" }).getAttribute("href"), + detail, + ); + } + const direct = await context.newPage(); + try { + const response = await direct.goto(`${origin.origin}${chat}?from=direct#saved-messages`); + assertEquals(response?.status(), 200); + await direct.getByText("No public messages yet.").waitFor(); + await direct.reload(); + await direct.getByText("No public messages yet.").waitFor(); + assertEquals( + new URL(direct.url()).pathname + new URL(direct.url()).search + new URL(direct.url()).hash, + `${chat}?from=direct#saved-messages`, + ); + } finally { + await direct.close(); + } + assertEquals(posts.length, 0, "route redirects and direct loads must not submit input"); const composer = page.getByRole("textbox", { name: `Message to ${input.subject_name}` }); await composer.fill("WEB_CONNECTED_FIRST_INPUT"); await page.getByRole("button", { name: "Send message", exact: true }).click(); @@ -59,6 +98,10 @@ try { { data: { ...retry, text: "changed payload under original operation" } }, ); assertEquals(conflict.status(), 409); + await page.goto(`${origin.origin}${legacy}/conversation${suffix}`); + await page.waitForURL(`${origin.origin}${chat}${suffix}`); + await page.getByText("Previous send outcome is unknown. Its original input is retained.") + .waitFor(); await page.reload(); await page.getByText("Previous send outcome is unknown. Its original input is retained.") .waitFor(); @@ -77,6 +120,13 @@ try { 0, ); assertEquals(await history.getByText("WEB_CONNECTED_BUSY_INPUT", { exact: true }).count(), 1); + for (const entry of [chat, `${legacy}/conversation`, `${legacy}/chat`]) { + const response = await page.goto(`${origin.origin}${entry}${suffix}`); + assertEquals(response?.status(), 200); + await page.waitForURL(`${origin.origin}${chat}${suffix}`); + await page.getByText("public reply 3", { exact: true }).waitFor(); + assertEquals(await history.getByRole("listitem").count(), 4); + } const deniedPath = `${origin.origin}${base}/${ encodeURIComponent(input.conversation_id) }/messages`; diff --git a/tools/web-ux/deno.json b/tools/web-ux/deno.json index c9ebdca89..357846ce8 100644 --- a/tools/web-ux/deno.json +++ b/tools/web-ux/deno.json @@ -1,6 +1,7 @@ { "lock": true, "imports": { + "#lib/": "../../web/workspace/src/lib/", "@std/assert": "jsr:@std/assert@1.0.19", "@std/path": "jsr:@std/path@1.1.4", "pixelmatch": "npm:pixelmatch@7.1.0",