Files

86 lines
4.1 KiB
JavaScript

// Row 164: the chat switch and fixed recovery message stay local to chat HTTP.
import test from "node:test";
import assert from "node:assert/strict";
import { request } from "node:http";
import { createChat } from "../src/chat.mjs";
import { startServer } from "../src/serve.mjs";
import { BusReadError } from "../src/bus.mjs";
import { close } from "./fixture.mjs";
import { createSsoSession } from "./helpers/sso-session.mjs";
const P = "10000000-0000-4000-8000-000000000001";
const T = "20000000-0000-4000-8000-000000000002";
function http(port, headers, path, body) {
const raw = body === undefined ? undefined : JSON.stringify(body);
return new Promise((resolve, reject) => {
const req = request({ host: '127.0.0.1', port, path, method: raw === undefined ? 'GET' : 'POST',
headers: { ...headers, ...(raw === undefined ? {} : { 'content-length': Buffer.byteLength(raw) }) },
}, res => {
let text = '';
res.on('data', chunk => text += chunk);
res.on('end', () => resolve({ status: res.statusCode, data: JSON.parse(text) }));
});
req.on('error', reject); req.end(raw);
});
}
async function setup(t, selected) {
const calls = [], logs = [];
const auth = await createSsoSession(t, { host: 'chat.example.com' });
const chat = createChat({ call: async (verb, args) => {
calls.push([verb, args]);
if (verb === "plugins") return { business: "demo", revision: "a".repeat(64), plugins: selected === undefined ? {} : { channel: selected } };
return [];
} });
const server = await startServer({ port: 0, chat, sso: auth.sso, send: async () => ({}), log: line => logs.push(line) });
t.after(() => close(server));
const session = await auth.signIn(server.address().port);
const headers = auth.writeHeaders(session);
return { calls, logs, async request(path, body) {
return http(server.address().port, headers, path, body);
} };
}
test("WebUI channel off returns 503 and named recovery for every chat route without a chat operation", async t => {
for (const selected of [[], undefined, ["cli"]]) {
const s = await setup(t, selected);
for (const [path, body] of [
["/api/chat/projects"], ["/api/chat/threads"], [`/api/chat/threads/${T}/messages`],
["/api/chat/projects", { name: "work" }], ["/api/chat/threads", { project: P, name: "chat" }],
[`/api/chat/threads/${T}/messages`, { body: "private body" }], [`/api/chat/threads/${T}/open`, {}],
]) {
s.calls.length = 0;
const res = await s.request(path, body);
assert.equal(res.status, 503);
assert.equal(res.data.error, "channel-disabled");
assert.match(res.data.message, /mosaic plugins.*mosaic plugins set/s);
assert.match(res.data.message, /plugins\.channel/);
assert.match(res.data.message, /cli.*webui/);
if (body !== undefined) assert.equal(res.data.sent, false);
assert.deepEqual(s.calls, [["plugins", {}]]);
assert.doesNotMatch(JSON.stringify(res.data), /private body/);
}
}
});
test("WebUI only selection keeps web chat enabled while retaining the old read envelope", async t => {
const s = await setup(t, ["webui"]);
const res = await s.request("/api/chat/projects");
assert.equal(res.status, 200);
assert.deepEqual(res.data.rows, []);
assert.deepEqual(s.calls, [["plugins", {}], ["project.list", {}]]);
});
test("WebUI channel-disabled recovery text is fixed and never echoes transport text", async t => {
const chat = createChat({ call: async () => { throw new BusReadError("channel-disabled", "SENTINEL-private-input"); } });
const auth = await createSsoSession(t, { host: 'chat.example.com' });
const server = await startServer({ port: 0, chat, sso: auth.sso, send: async () => ({}), log: () => {} });
t.after(() => close(server));
const session = await auth.signIn(server.address().port);
const res = await http(server.address().port, auth.writeHeaders(session), '/api/chat/projects');
const result = res.data;
assert.equal(res.status, 503);
assert.equal(result.error, "channel-disabled");
assert.match(result.message, /mosaic plugins.*mosaic plugins set/s);
assert.doesNotMatch(JSON.stringify(result), /SENTINEL/);
});