test: prepare connected chat checks for canonical and legacy Subject routes

This commit is contained in:
2026-10-31 14:40:53 +09:00
parent 9e7392256b
commit 7a2d132c16
3 changed files with 54 additions and 3 deletions
@@ -111,7 +111,7 @@ async fn chat_web_connected_busy_unknown_replay_and_authorization_use_real_runti
.kill_on_drop(true) .kill_on_drop(true)
.spawn() .spawn()
.expect("explicit browser test requires Deno"); .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(); let mut stdin = child.stdin.take().unwrap();
stdin.write_all(&wire).await.unwrap(); stdin.write_all(&wire).await.unwrap();
drop(stdin); drop(stdin);
@@ -3,11 +3,21 @@
// deliberate lost transport acknowledgement are substituted. No live instance. // deliberate lost transport acknowledgement are substituted. No live instance.
import { assert, assertEquals } from "@std/assert"; import { assert, assertEquals } from "@std/assert";
import { chromium } from "playwright"; 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 input = JSON.parse(await new Response(Deno.stdin.readable).text());
const origin = new URL(input.origin); const origin = new URL(input.origin);
assert(origin.hostname === "127.0.0.1" && origin.protocol === "http:"); assert(origin.hostname === "127.0.0.1" && origin.protocol === "http:");
const base = `/api/w/${encodeURIComponent(input.workspace_id)}/chat/conversations`; 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 }); const browser = await chromium.launch({ headless: true });
try { try {
const context = await browser.newContext({ const context = await browser.newContext({
@@ -25,10 +35,39 @@ try {
`${base}/${encodeURIComponent(input.conversation_id)}/messages` `${base}/${encodeURIComponent(input.conversation_id)}/messages`
) posts.push(request.postDataJSON()); ) 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); 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(); 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}` }); const composer = page.getByRole("textbox", { name: `Message to ${input.subject_name}` });
await composer.fill("WEB_CONNECTED_FIRST_INPUT"); await composer.fill("WEB_CONNECTED_FIRST_INPUT");
await page.getByRole("button", { name: "Send message", exact: true }).click(); await page.getByRole("button", { name: "Send message", exact: true }).click();
@@ -59,6 +98,10 @@ try {
{ data: { ...retry, text: "changed payload under original operation" } }, { data: { ...retry, text: "changed payload under original operation" } },
); );
assertEquals(conflict.status(), 409); 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.reload();
await page.getByText("Previous send outcome is unknown. Its original input is retained.") await page.getByText("Previous send outcome is unknown. Its original input is retained.")
.waitFor(); .waitFor();
@@ -77,6 +120,13 @@ try {
0, 0,
); );
assertEquals(await history.getByText("WEB_CONNECTED_BUSY_INPUT", { exact: true }).count(), 1); 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}/${ const deniedPath = `${origin.origin}${base}/${
encodeURIComponent(input.conversation_id) encodeURIComponent(input.conversation_id)
}/messages`; }/messages`;
+1
View File
@@ -1,6 +1,7 @@
{ {
"lock": true, "lock": true,
"imports": { "imports": {
"#lib/": "../../web/workspace/src/lib/",
"@std/assert": "jsr:@std/assert@1.0.19", "@std/assert": "jsr:@std/assert@1.0.19",
"@std/path": "jsr:@std/path@1.1.4", "@std/path": "jsr:@std/path@1.1.4",
"pixelmatch": "npm:pixelmatch@7.1.0", "pixelmatch": "npm:pixelmatch@7.1.0",