Files
workbuddy_skills/draw-ui/scripts/credential-ui/tests/multi-key.test.ts
T
admin 237a09a5b0 修:draw-ui / oil-motion 原被当子模块指针收录 ⇒ 改为正常文件入库(两份内容原先对别人是空的)
一、问题(本轮实测)
`draw-ui` 与 `oil-motion` 目录里**各自带一个内嵌 `.git`** ⇒ 上一次提交把它们记成了 **gitlink(子模块指针)**
⇒ 仓库里只存了一个不属于任何远端的 commit id,**别人克隆下来这两份是空的** ✗(`git status` 显示 ` m draw-ui` / ` m oil-motion` = 子模块内容有改动)。

二、处置(可回退)
· 把两处的 `.git` **挪走**(⛔ 不是删除)⇒ `归档/内嵌git-20261008/{draw-ui,oil-motion}.git`;
· `git rm --cached` 掉那两个 gitlink,再 `git add` 两个目录 ⇒ **按正常文件入库**(内容才真的进仓库)。

三、副作用(如实记)
挪走 `.git` 后,这两个技能**不能再原地 `git pull` 取上游更新**(要更新得重新拉一份覆盖);
如需恢复其本地仓库,把 `归档/内嵌git-20261008/` 里的 `.git` 挪回原处即可。
2026-10-08 22:29:52 +08:00

137 lines
9.7 KiB
TypeScript

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createStore, type CredentialBackend, type Manifest } from '../src/config.ts';
import { startServer } from '../src/server.ts';
const manifest = (name: string): Manifest => ({ version: 1, id: 'sample-skill', label: name, credential: 'sample-skill/' + name });
function memory() {
const values = new Map<string, string>();
const backend = (ref: string): CredentialBackend => ({
name: '测试凭据库', get: async () => values.get(ref),
set: async value => { values.set(ref, value); }, delete: async () => { values.delete(ref); }
});
return { values, backend };
}
test('同一个 Skill 的多个 key 独立保存、替换和删除', async () => {
const m = memory(), a = manifest('first'), b = manifest('second');
const sa = createStore(a, m.backend(a.credential)), sb = createStore(b, m.backend(b.credential));
await sa.save({ value: 'FAKE_FIRST_VALUE', revision: (await sa.status()).revision });
await sb.save({ value: 'FAKE_SECOND_VALUE', revision: (await sb.status()).revision });
await sa.save({ value: 'FAKE_REPLACED_VALUE', revision: (await sa.status()).revision, replaceExisting: true });
assert.equal(m.values.get(b.credential), 'FAKE_SECOND_VALUE');
await m.backend(a.credential).delete();
assert.equal((await sa.status()).configured, false); assert.equal((await sb.status()).configured, true);
});
test('多个端口共享浏览器 cookie 容器时,会话仍互不覆盖', async t => {
const m = memory(), a = manifest('first'), b = manifest('second');
const one = await startServer({ manifest: a, backend: m.backend(a.credential) });
const two = await startServer({ manifest: b, backend: m.backend(b.credential) });
t.after(one.close); t.after(two.close);
const jar = new Map<string, string>();
for (const app of [one, two]) {
const auth = await fetch(app.origin + '/api/session', { method: 'POST',
headers: { Origin: app.origin, Authorization: 'Bearer ' + app.bootstrap, 'X-Local-Request': '1' } });
assert.equal(auth.status, 200);
const cookie = auth.headers.get('set-cookie')!.split(';')[0], split = cookie.indexOf('=');
jar.set(cookie.slice(0, split), cookie.slice(split + 1));
}
assert.equal(jar.size, 2);
const cookie = [...jar].map(([k, v]) => k + '=' + v).join('; ');
for (const [app, ref, value] of [[one, a.credential, 'FAKE_FIRST_VALUE'], [two, b.credential, 'FAKE_SECOND_VALUE']] as const) {
const headers = { Cookie: cookie, Origin: app.origin, 'X-Local-Request': '1', 'Content-Type': 'application/json' };
const response = await fetch(app.origin + '/api/meta', { headers }); assert.equal(response.status, 200);
const meta = await response.json();
const saved = await fetch(app.origin + '/api/save', { method: 'POST', headers, body: JSON.stringify({ revision: meta.revision, value }) });
assert.equal(saved.status, 200);
const status = await (await fetch(app.origin + '/agent/status', { headers: { Authorization: 'Bearer ' + app.bootstrap } })).json();
assert.equal(status.credential, ref); assert.equal(status.status, 'saved');
}
assert.equal(m.values.size, 2);
assert.equal((await fetch(one.origin + '/agent/status', { headers: { Authorization: 'Bearer ' + two.bootstrap } })).status, 401);
});
test('同一凭据的旧会话在其他会话保存后不能覆盖', async () => {
const m = memory(), a = manifest('shared');
const first = createStore(a, m.backend(a.credential)), second = createStore(a, m.backend(a.credential));
const initial = await second.status();
await first.save({ revision: (await first.status()).revision, value: 'FAKE_LATEST_VALUE' });
await assert.rejects(second.save({ revision: initial.revision, value: 'FAKE_STALE_VALUE', replaceExisting: true }), /发生变化/);
assert.equal(m.values.get(a.credential), 'FAKE_LATEST_VALUE');
});
async function session(app: Awaited<ReturnType<typeof startServer>>) {
const headers: Record<string, string> = { Origin: app.origin, Authorization: 'Bearer ' + app.bootstrap,
'X-Local-Request': '1', 'Content-Type': 'application/json' };
const auth = await fetch(app.origin + '/api/session', { method: 'POST', headers });
headers.Cookie = auth.headers.get('set-cookie')!.split(';')[0];
return {
meta: async () => (await fetch(app.origin + '/api/meta', { headers })).json(),
save: (entries: object[]) => fetch(app.origin + '/api/save', { method: 'POST', headers, body: JSON.stringify({ entries }) }),
agent: async () => (await fetch(app.origin + '/agent/status', { headers })).json(),
};
}
test('同页多 key 真实 HTTP 保存、文案与状态脱敏', async t => {
const m = memory(), fields = [manifest('one'), manifest('two')], emitted: object[] = [];
const app = await startServer({ manifests: fields, backends: fields.map(field => m.backend(field.credential)),
ui: { title: '连接服务', label: '双服务配置', saveLabel: '确认保存' }, onComplete: value => emitted.push(value) });
t.after(app.close); const client = await session(app); const meta = await client.meta();
assert.equal(meta.fields.length, 2); assert.equal(meta.page.title, '连接服务');
const response = await client.save(meta.fields.map((field: any, index: number) => ({ credential: field.credential, revision: field.revision, value: 'FAKE_BATCH_' + index })));
assert.equal(response.status, 200); const result = await response.json(); assert.equal(result.status, 'saved');
assert.equal(m.values.get(fields[0].credential), 'FAKE_BATCH_0'); assert.equal(m.values.get(fields[1].credential), 'FAKE_BATCH_1');
assert.equal(JSON.stringify([result, await client.meta(), await client.agent(), emitted]).includes('FAKE_BATCH_'), false);
});
test('整组预检拒绝无效项、重复项、越界引用、未确认替换和缺失项,零写入', async t => {
const m = memory(), fields = [manifest('one'), manifest('two')];
m.values.set(fields[0].credential, 'FAKE_OLD');
const app = await startServer({ manifests: fields, backends: fields.map(field => m.backend(field.credential)) });
t.after(app.close); const client = await session(app); const meta = await client.meta();
const entries = meta.fields.map((field: any) => ({ credential: field.credential, revision: field.revision, value: 'FAKE_NEW', replaceExisting: true }));
for (const invalid of [
[entries[0]], [entries[0], { ...entries[1], value: 'multi\nline' }],
[entries[0], entries[0]], [entries[0], { ...entries[1], credential: 'not/in/page' }],
[{ ...entries[0], replaceExisting: false }, entries[1]],
[entries[0], { ...entries[1], revision: 'stale' }],
[entries[0], { ...entries[1], target: '/tmp/plaintext.json' }],
]) { assert.ok((await client.save(invalid)).status >= 400); assert.deepEqual([...m.values], [[fields[0].credential, 'FAKE_OLD']]); }
});
test('已有项留空不覆盖,中途失败保留已保存项并可仅重试失败项', async t => {
const m = memory(), fields = [manifest('one'), manifest('two'), manifest('three'), manifest('four')];
m.values.set(fields[0].credential, 'FAKE_KEEP');
const backends = fields.map(field => m.backend(field.credential));
let fail = true; const original = backends[2].set;
backends[2].set = async value => { if (fail) throw Error(value); await original(value); };
const app = await startServer({ manifests: fields, backends }); t.after(app.close);
const client = await session(app); let meta = await client.meta();
const response = await client.save(meta.fields.slice(1).map((field: any) => ({ credential: field.credential, revision: field.revision, value: 'FAKE_BATCH' })));
const result = await response.json(); assert.equal(result.status, 'partial');
assert.deepEqual(result.results.map((item: any) => item.status), ['saved', 'failed', 'not_attempted']);
assert.equal(m.values.has(fields[3].credential), false);
assert.equal((await client.agent()).status, 'partial'); assert.equal(JSON.stringify(result).includes('FAKE_BATCH'), false);
assert.equal(m.values.get(fields[0].credential), 'FAKE_KEEP'); assert.equal(m.values.get(fields[1].credential), 'FAKE_BATCH');
fail = false; meta = await client.meta();
const retry = await client.save(meta.fields.slice(2).map((field: any) => ({ credential: field.credential, revision: field.revision, value: 'FAKE_RETRY' })));
assert.equal((await retry.json()).status, 'saved'); assert.equal(m.values.get(fields[1].credential), 'FAKE_BATCH');
});
test('16 项预检并行读取,写入仍顺序执行且每项写前复验', async t => {
const fields = Array.from({ length: 16 }, (_, index) => manifest('key-' + index));
let active = 0, peak = 0, reads = 0, writes = 0;
const backends = fields.map((): CredentialBackend => ({ name: '测试凭据库',
get: async () => {
reads++; active++; peak = Math.max(peak, active);
await new Promise(resolve => setTimeout(resolve, 2)); active--; return undefined;
},
set: async () => { assert.equal(active, 0); writes++; }, delete: async () => {},
}));
const app = await startServer({ manifests: fields, backends }); t.after(app.close);
const client = await session(app); const meta = await client.meta();
peak = 0; reads = 0;
const response = await client.save(meta.fields.map((field: any) => ({ credential: field.credential, revision: field.revision, value: 'FAKE_ONLY' })));
assert.equal((await response.json()).status, 'saved');
assert.equal(peak, 16); assert.equal(reads, 32); assert.equal(writes, 16);
});
test('页面拒绝重复引用、超限字段和空配置', async () => {
const m = memory(), field = manifest('one');
for (const fields of [[], [field, field], Array.from({ length: 17 }, (_, index) => manifest('key-' + index))])
await assert.rejects(startServer({ manifests: fields, backends: fields.map(item => m.backend(item.credential)) }));
});