Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
87 lines
5.6 KiB
Diff
87 lines
5.6 KiB
Diff
diff --git a/mnt/storage/src/mosaic-stack-rocko-129-r1/packages/webui/tests/chat-browser.test.mjs b/mnt/storage/src/mosaic-stack-rocko-129-r2/packages/webui/tests/chat-browser.test.mjs
|
|
index a602883fe..f3eeeca96 100644
|
|
--- a/mnt/storage/src/mosaic-stack-rocko-129-r1/packages/webui/tests/chat-browser.test.mjs
|
|
+++ b/mnt/storage/src/mosaic-stack-rocko-129-r2/packages/webui/tests/chat-browser.test.mjs
|
|
@@ -63,6 +63,9 @@ test('chat browser: list/create named projects and threads, hostile labels inert
|
|
assert.equal(await b.evaluate('window.injected'), undefined);
|
|
assert.equal(await b.evaluate('!!document.querySelector(".chat-projects img")'), false);
|
|
assert.match(await b.evaluate('document.querySelector(".chat-projects").textContent'), /\\u001b|␛/);
|
|
+ const projectText = await b.evaluate('document.querySelector(".chat-projects").textContent');
|
|
+ assert.doesNotMatch(projectText, /[\u202a-\u202e\u2066-\u2069]/u);
|
|
+ assert.match(projectText, /\\u202e/);
|
|
assert.equal(await b.evaluate(`!!document.querySelector('#sections a[href="#/chat"]')`), true);
|
|
await b.key('k', 'KeyK', 2);
|
|
await wait('document.querySelector("#cmdk-dialog").open');
|
|
@@ -74,11 +77,13 @@ test('chat browser: list/create named projects and threads, hostile labels inert
|
|
await click('#chat-project-form button[type=submit]');
|
|
await wait('document.querySelector("#chat-thread-form")');
|
|
assert.deepEqual(await b.evaluate('window.__writes[0].body'), { name: 'new project', repo: 'stack' });
|
|
+ assert.equal(await b.evaluate('window.__writes[0].path'), '/api/chat/projects');
|
|
await set('#chat-thread-name', 'named thread');
|
|
await b.evaluate(`window.__answers.push({body:{id:'${A}',instance:'assistant'}}),true`);
|
|
await click('#chat-thread-form button[type=submit]');
|
|
await wait('document.querySelector("#chat-send-form")');
|
|
assert.deepEqual(await b.evaluate('window.__writes[1].body'), { project: P, name: 'named thread' });
|
|
+ assert.equal(await b.evaluate('window.__writes[1].path'), '/api/chat/threads');
|
|
assert.equal(await b.evaluate('window.__writes.length'), 2);
|
|
});
|
|
test('chat browser: thread drafts, receipts and cursor history stay isolated; refresh never writes', async t => {
|
|
@@ -86,6 +91,9 @@ test('chat browser: thread drafts, receipts and cursor history stay isolated; re
|
|
await b.evaluate(`window.__pages.push({rows:[{id:'${M}',body:${JSON.stringify(HOSTILE)},from_role:'human',at:'2026-10-18T00:00:00Z'}],after:'${M}',hasMore:true}),true`);
|
|
await route(A);
|
|
assert.equal(await b.evaluate('!!document.querySelector(".chat-log img")'), false);
|
|
+ const messageText = await b.evaluate('document.querySelector(".chat-log").textContent');
|
|
+ assert.doesNotMatch(messageText, /[\u202a-\u202e\u2066-\u2069]/u);
|
|
+ assert.match(messageText, /\\u202e/);
|
|
await set('#chat-body', 'alpha draft');
|
|
await b.evaluate('(()=>{const e=document.querySelector("#chat-body");e.focus();e.setSelectionRange(2,5);return true})()');
|
|
await refresh();
|
|
@@ -100,9 +108,11 @@ test('chat browser: thread drafts, receipts and cursor history stay isolated; re
|
|
assert.doesNotMatch(await b.evaluate('document.querySelector("#chat-receipt").textContent'), /Accepted/);
|
|
assert.deepEqual(await b.evaluate('window.__writes.map(x=>x.body)'), [{body:'alpha draft'}]);
|
|
assert.equal(await b.evaluate('window.__writes[0].redirect'), 'manual');
|
|
+ assert.equal(await b.evaluate('window.__writes[0].path'), `/api/chat/threads/${A}/messages`);
|
|
await b.evaluate('window.__answers.push({body:{opened:location.hash.split("/").at(-1)}}),true');
|
|
await click('#chat-resume'); await wait('document.querySelector("#chat-receipt")?.textContent.includes("requested")');
|
|
assert.equal(await b.evaluate('window.__writes.length'), 2);
|
|
+ assert.equal(await b.evaluate('window.__writes[1].path'), `/api/chat/threads/${B}/open`);
|
|
});
|
|
test('chat browser: uncertain writes retain draft, ownership refusal clears it, read-only cannot send', async t => {
|
|
const { b, wait, set, click, route, refresh } = await setup(t);
|
|
diff --git a/mnt/storage/src/mosaic-stack-rocko-129-r1/packages/webui/tests/chat.test.mjs b/mnt/storage/src/mosaic-stack-rocko-129-r2/packages/webui/tests/chat.test.mjs
|
|
index 699ad9c51..00ee8326a 100644
|
|
--- a/mnt/storage/src/mosaic-stack-rocko-129-r1/packages/webui/tests/chat.test.mjs
|
|
+++ b/mnt/storage/src/mosaic-stack-rocko-129-r2/packages/webui/tests/chat.test.mjs
|
|
@@ -118,6 +118,31 @@ for (const [path, value, verb, args, answer] of posts) {
|
|
});
|
|
}
|
|
|
|
+test('chat createThread receipt requires an instance even when its id is valid', async t => {
|
|
+ const c = await setup(t);
|
|
+ for (const answer of [{ id: T }, { id: T, instance: 'invalid/instance' }, { id: T, instance: 3 }]) {
|
|
+ c.state.answer = answer;
|
|
+ const before = c.calls.length;
|
|
+ const result = await c.get('/api/chat/threads', { method: 'POST', value: { project: P, name: 'discussion' } });
|
|
+ assert.equal(result.status, 503);
|
|
+ assert.equal(result.data.error, 'outcome-unknown');
|
|
+ assert.equal(result.data.sent, 'unknown');
|
|
+ assert.equal(c.calls.length, before + 1);
|
|
+ }
|
|
+});
|
|
+
|
|
+test('chat write receipts expose only the fields belonging to their operation', async t => {
|
|
+ for (const [path, value, , , answer] of posts) {
|
|
+ const c = await setup(t);
|
|
+ c.state.answer = { owner: 'private owner', capability: 'not-a-capability', body: 'private body', ...answer };
|
|
+ const result = await c.get(path, { method: 'POST', value });
|
|
+ assert.equal(result.status, 200);
|
|
+ const { at, ...receipt } = result.data;
|
|
+ assert.equal(typeof at, 'string');
|
|
+ assert.deepEqual(receipt, answer);
|
|
+ }
|
|
+});
|
|
+
|
|
test('chat validates names, body, project and strict empty open payload', async t => {
|
|
const c = await setup(t);
|
|
for (const [path, value] of [
|