修: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` 挪回原处即可。
This commit is contained in:
1 parent
e03465c398
commit
237a09a5b0
161 files changed
+19429
-2
No files matched your search
@@ -0,0 +1,136 @@
|
||||
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)) }));
|
||||
});
|
||||
Reference in new issue
Block a user