test: connect Subject Web conversation to real Backend and Runtime

This commit is contained in:
2026-10-31 14:34:23 +09:00
parent 24694dfddc
commit 54dea496ff
3 changed files with 289 additions and 4 deletions
@@ -0,0 +1,98 @@
// Explicit connected test driver. Rust owns ephemeral Backend/Runtime/SQLite;
// this browser runs the production Web build. Only the external model and
// deliberate lost transport acknowledgement are substituted. No live instance.
import { assert, assertEquals } from "@std/assert";
import { chromium } from "playwright";
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 browser = await chromium.launch({ headless: true });
try {
const context = await browser.newContext({
extraHTTPHeaders: { Authorization: `Bearer ${input.owner_token}` },
});
const page = await context.newPage();
page.setDefaultTimeout(30000);
const posts: Record<string, unknown>[] = [];
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
page.on("request", (request) => {
if (
request.method() === "POST" &&
new URL(request.url()).pathname ===
`${base}/${encodeURIComponent(input.conversation_id)}/messages`
) posts.push(request.postDataJSON());
});
const response = await page.goto(`${origin.origin}${input.subject_path}`);
assertEquals(response?.status(), 200);
await page.getByRole("link", { name: "Open conversation" }).click();
await page.getByText("No public messages yet.").waitFor();
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();
await page.getByText("Message saved; Runtime acceptance is unknown. Retry only this same input.")
.waitFor();
await page.getByRole("button", { name: "Retry same input" }).click();
await page.getByText("Runtime accepted this input. Processing and replies are not confirmed.")
.waitFor();
assertEquals(posts.length, 2);
assertEquals(posts[0], posts[1]);
let lost = false;
await page.route(`**${base}/**/messages`, async (route) => {
if (!lost && route.request().method() === "POST") {
lost = true;
const actual = await route.fetch();
assertEquals(actual.status(), 200);
await route.abort("failed");
} else await route.continue();
});
await composer.fill("WEB_CONNECTED_BUSY_INPUT");
await page.getByRole("button", { name: "Send message", exact: true }).click();
await page.getByRole("alert").filter({ hasText: /outcome could not be confirmed/ }).waitFor();
await composer.fill("edited next draft must not replace retained input");
assert(await page.getByRole("button", { name: "Send message", exact: true }).isDisabled());
const retry = posts[2];
const conflict = await context.request.post(
`${origin.origin}${base}/${encodeURIComponent(input.conversation_id)}/messages`,
{ data: { ...retry, text: "changed payload under original operation" } },
);
assertEquals(conflict.status(), 409);
await page.reload();
await page.getByText("Previous send outcome is unknown. Its original input is retained.")
.waitFor();
await page.getByRole("button", { name: "Retry same input" }).click();
await page.getByText("Runtime accepted this input. Processing and replies are not confirmed.")
.waitFor();
assertEquals(posts.length, 4);
assertEquals(posts[2], posts[3]);
await page.getByRole("button", { name: "Refresh history", exact: true }).click();
await page.getByText("public reply 2", { exact: true }).waitFor();
await page.getByText("public reply 3", { exact: true }).waitFor();
const history = page.getByRole("list", { name: "Saved public messages" });
assertEquals(await history.getByRole("listitem").count(), 4);
assertEquals(
await page.getByText("INTERNAL_ASSISTANT_TEXT_NOT_A_PUBLIC_CHAT_MESSAGE").count(),
0,
);
assertEquals(await history.getByText("WEB_CONNECTED_BUSY_INPUT", { exact: true }).count(), 1);
const deniedPath = `${origin.origin}${base}/${
encodeURIComponent(input.conversation_id)
}/messages`;
await context.setExtraHTTPHeaders({ Authorization: `Bearer ${input.foreign_token}` });
const denied = await context.request.post(deniedPath, {
data: { operation_id: "foreign-cannot-post", text: "must not append" },
});
assertEquals(denied.status(), 403);
await page.getByRole("button", { name: "Refresh history", exact: true }).click();
await page.getByRole("alert").filter({ hasText: /do not have access/ }).waitFor();
assertEquals(await page.getByRole("textbox").count(), 0);
assertEquals(await history.getByRole("listitem").count(), 0);
assertEquals(pageErrors, []);
console.log(
"real Web -> Backend -> Runtime -> explicit public replies passed; scripted model, API-token browser transport; Body route not yet integrated",
);
} finally {
await browser.close();
}