|
|
@@ -59,15 +59,17 @@ async function appFixture() {
|
|
|
];
|
|
|
const raw = { ...blank(), session: sessions.map((s, i) => ({ objectId: s.objectId, studentName: s.studentName, meta: { participantKey: 'workshop-participant-' + i } })),
|
|
|
artifact: [{ objectId: 'a', ownerName: '历史占位名', ownerSession: 'session1', rawText: '正文不能修改' }], project: [{ objectId: 'p', ownerName: '历史占位名', ownerSession: 'session2' }] };
|
|
|
- const writes = [], audits = [];
|
|
|
+ const writes = [], audits = [], business = [];
|
|
|
const runtime = { objectId: 'runtime-id', key: 'runtime', value: structuredClone(settings) };
|
|
|
const store = { get: async (kind, id) => structuredClone(sessions.find((s) => s.objectId === id)), list: async () => [structuredClone(runtime)], lock: async () => {}, update: async (kind, id, patch) => { writes.push({ kind, id, patch }); Object.assign(runtime, patch); } };
|
|
|
const fail = (code) => { throw Object.assign(new Error(code), { code }); };
|
|
|
- const app = { handlers: { ksfGetPublicBoard: async () => structuredClone(raw) }, fail, hash, canonical,
|
|
|
+ const app = { handlers: { ksfGetPublicBoard: async () => structuredClone(raw),
|
|
|
+ ksfStartWorkshopSession: async (p) => { business.push({ name: 'start', p }); return { sessionId: 'new-session' }; },
|
|
|
+ ksfSaveWorkshopTask: async (p) => { business.push({ name: 'save', p }); return { artifactId: 'new-artifact' }; } }, fail, hash, canonical,
|
|
|
need: (actor, scope) => { if (!actor) fail('AUTH_REQUIRED'); if (!actor.scopes.some((s) => s === '*' || s === scope)) fail('FORBIDDEN'); },
|
|
|
operation: async (actor, name, p, fn) => { audits.push({ name, p }); return fn(store); } };
|
|
|
attachWecomEmployeeHandlers(app, { store, settings, fetchImpl: api.fetchImpl });
|
|
|
- return { app, raw, sessions, writes, audits, settings, api, runtime, actor: { id: 'admin', scopes: ['*'] } };
|
|
|
+ return { app, raw, sessions, writes, audits, business, settings, api, runtime, actor: { id: 'admin', scopes: ['*'] } };
|
|
|
}
|
|
|
test('public overlay supplies directory names and stable identity across workshops while preserving history', async () => {
|
|
|
const f = await appFixture(), before = structuredClone(f.raw), result = await f.app.handlers.ksfGetPublicBoard({});
|
|
|
@@ -105,3 +107,25 @@ test('same-name employees are kept separate even in one department, without losi
|
|
|
const raw = { ...blank(), session: [{ objectId: 's1', employee: employee('a'), meta: { participantKey: 'p1' } }, { objectId: 's2', employee: employee('b'), meta: { participantKey: 'p2' } }], artifact: [{ objectId: 'a', ownerSession: 's2' }] };
|
|
|
const db = buildModel(raw); assert.equal(db.members.length, 2); assert.equal(db.members.find((m) => m.name.includes('bbbbbbbb')).artifactIds.length, 1);
|
|
|
});
|
|
|
+test('digital-human start and report upload automatically resolve the employee without a separate login or identity API call', async () => {
|
|
|
+ const f = await appFixture(); f.settings.wecomEmployees = {};
|
|
|
+ const actor = { id: 'service1', scopes: ['ingest:write'] };
|
|
|
+ const started = await f.app.handlers.ksfStartWorkshopSession({ requestId: 'start-1', participant: { identityBasis: 'channel_context', account: account() }, agent: { account: { platform: 'wecom', accountId: 'test-bot' } }, conversation: { channel: 'wecom' } }, actor);
|
|
|
+ assert.equal(started.employee.name, '测试成员甲'); assert.equal(started.employeeIdentity.status, 'verified'); assert.equal(started.employeeIdentity.automatic, true);
|
|
|
+ const uploaded = await f.app.handlers.ksfSaveWorkshopTask({ requestId: 'save-1', sessionId: 'session1', title: '新报告' }, actor);
|
|
|
+ assert.equal(uploaded.artifactId, 'new-artifact'); assert.equal(uploaded.employee.name, '测试成员甲'); assert.equal(f.business.length, 2);
|
|
|
+ assert.equal(f.audits.length, 2); assert.ok(f.audits.every((a) => a.name === 'wecom-employee-sync'));
|
|
|
+ assert.equal(f.business[1].p.requestId, 'save-1'); assert.equal(f.sessions[0].studentName, '历史占位名');
|
|
|
+});
|
|
|
+test('upload uses the saved sender account, refuses wrong bot and stops failed configured lookups before saving the report', async () => {
|
|
|
+ const f = await appFixture();
|
|
|
+ await assert.rejects(() => f.app.handlers.ksfStartWorkshopSession({ requestId: 'wrong-bot', participant: { identityBasis: 'channel_context', account: account() }, agent: { account: { platform: 'wecom', accountId: 'other-bot' } }, conversation: { channel: 'wecom' } }, f.actor), { code: 'IDENTITY_CONFLICT' });
|
|
|
+ delete f.settings.wecomDirectory.bots['test-bot'].userIds['opaque-test-sender'];
|
|
|
+ await assert.rejects(() => f.app.handlers.ksfSaveWorkshopTask({ requestId: 'bad-map', sessionId: 'session1', account: { name: '正文伪造姓名' } }, f.actor), { code: 'WECOM_SENDER_MAPPING_REQUIRED' });
|
|
|
+ assert.equal(f.business.length, 0); assert.equal(f.writes.length, 0);
|
|
|
+});
|
|
|
+test('unconfigured name service keeps sender attribution and marks the upload receipt pending instead of claiming a real name', async () => {
|
|
|
+ const f = await appFixture(); delete f.settings.wecomDirectory; f.settings.wecomEmployees = {};
|
|
|
+ const result = await f.app.handlers.ksfSaveWorkshopTask({ requestId: 'pending-save', sessionId: 'session1' }, f.actor);
|
|
|
+ assert.equal(result.artifactId, 'new-artifact'); assert.equal(result.employeeIdentity.status, 'pending'); assert.equal(result.employee.nameVerified, false); assert.equal(f.writes.length, 0);
|
|
|
+});
|