|
@@ -0,0 +1,170 @@
|
|
|
|
|
+import test from 'node:test';
|
|
|
|
|
+import assert from 'node:assert/strict';
|
|
|
|
|
+import fs from 'node:fs';
|
|
|
|
|
+import os from 'node:os';
|
|
|
|
|
+import path from 'node:path';
|
|
|
|
|
+import {registerAgentProfile,readAgentProfileByKey,checkAgentProfile,registerParticipantProfile,registerChannelParticipant,readParticipantProfile,checkParticipantProfile,findParticipantProfiles,listIdentityProfiles,adoptLegacyAgentProfile,bindOwnAgent,bindRegisteredParticipant,redactIdentity,executeIdentity} from '../skills/ksf-workshop-runtime/scripts/ksf-identity.mjs';
|
|
|
|
|
+import {runWorkshop} from '../skills/ksf-workshop-runtime/scripts/ksf-workshop.mjs';
|
|
|
|
|
+import {registerBridgeIdentity} from '../skills/ksf-workshop-runtime/scripts/ksf-identity-bridge.mjs';
|
|
|
|
|
+const agentInput={name:'新数字人',role:'workshop',identityBasis:'operator_registration',runtimePlatform:'hermes',channel:'wecom',account:{platform:'wecom',tenantId:null,accountId:null,accountName:null,runtimeAgentId:null,wecomAppAgentId:null}};
|
|
|
|
|
+const employeeInput={name:'新员工',identityBasis:'participant_registration',account:{platform:'wecom',tenantId:null,accountId:null,accountName:null,employeeNumber:null}};
|
|
|
|
|
+function directory(t){const dir=fs.mkdtempSync(path.join(os.tmpdir(),'ksf-identity-test-'));t.after(()=>{assert.equal(path.dirname(dir),fs.realpathSync(os.tmpdir()));assert.ok(path.basename(dir).startsWith('ksf-identity-test-'));fs.rmSync(dir,{recursive:true});});return dir;}
|
|
|
|
|
+const dirs=root=>({profileDir:path.join(root,'agents'),participantDir:path.join(root,'participants')});
|
|
|
|
|
+test('新 Agent 自助登记生成通用稳定 UUID、同资料重用,密钥字段拒绝',t=>{
|
|
|
|
|
+ const root=directory(t),opts=dirs(root),a=registerAgentProfile(agentInput,opts),before=fs.readFileSync(path.join(opts.profileDir,a.profile.key.split(':')[1]+'.json'));
|
|
|
|
|
+ assert.match(a.profile.key,/^agent-registration:/);assert.equal(a.changed,true);assert.equal(registerAgentProfile({...agentInput,reuseKey:a.profile.key},opts).changed,false);assert.equal(registerAgentProfile(agentInput,opts).needsReview,true);assert.equal(readAgentProfileByKey(a.profile.key,opts.profileDir).name,'新数字人');
|
|
|
|
|
+ assert.equal(checkAgentProfile(a.profile.key,{...agentInput,collectedAt:'2026-11-02T02:00:00.000Z'},opts).matches,true);
|
|
|
|
|
+ assert.deepEqual(fs.readFileSync(path.join(opts.profileDir,a.profile.key.split(':')[1]+'.json')),before);
|
|
|
|
|
+ const duplicate=registerAgentProfile({...agentInput,name:'新数字人',account:{...agentInput.account,accountName:'其他账号'}},opts);assert.equal(duplicate.needsReview,true);assert.equal(duplicate.candidates.length,1);
|
|
|
|
|
+ assert.throws(()=>registerAgentProfile({...agentInput,account:{...agentInput.account,token:'fixture'}},opts),{code:'INVALID_IDENTITY_FIELDS'});
|
|
|
|
|
+ const separate=registerAgentProfile({...agentInput,confirmNew:true},opts);assert.equal(separate.changed,true);assert.notEqual(separate.profile.key,a.profile.key);
|
|
|
|
|
+});
|
|
|
|
|
+test('真实 Agent ID 生成可校验的账号摘要;稳定 ID 冲突拒绝',t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),input={...agentInput,identityBasis:'runtime_config',account:{...agentInput.account,accountId:'fixture-agent-account'}};
|
|
|
|
|
+ const p=registerAgentProfile(input,opts).profile;assert.match(p.key,/^agent-account:[a-f0-9]{64}$/);assert.equal(readAgentProfileByKey(p.key,opts.profileDir).key,p.key);
|
|
|
|
|
+ assert.throws(()=>registerAgentProfile({...input,name:'changed'},opts),{code:'AGENT_PROFILE_CONFLICT'});
|
|
|
|
|
+ assert.throws(()=>checkAgentProfile(p.key,{...input,runtimePlatform:'changed'},opts),{code:'AGENT_PROFILE_CONFLICT'});
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('可信来源必须有稳定 ID,篡改 UUID 档案的来源不能冒充运行配置',t=>{
|
|
|
|
|
+ const opts=dirs(directory(t));
|
|
|
|
|
+ for(const identityBasis of ['runtime_config','channel_context'])assert.throws(()=>registerAgentProfile({...agentInput,identityBasis},opts),{code:'RUNTIME_AGENT_ID_REQUIRED'});
|
|
|
|
|
+ for(const identityBasis of ['channel_context','existing_record'])assert.throws(()=>registerParticipantProfile({...employeeInput,identityBasis},opts),{code:'STABLE_EMPLOYEE_ID_REQUIRED'});
|
|
|
|
|
+ assert.throws(()=>registerParticipantProfile({...employeeInput,identityBasis:'channel_context',account:{...employeeInput.account,accountId:'user-without-corp'}},opts),{code:'STABLE_EMPLOYEE_ID_REQUIRED'});
|
|
|
|
|
+ const agent=registerAgentProfile(agentInput,opts).profile,file=path.join(opts.profileDir,agent.key.split(':')[1]+'.json'),row=JSON.parse(fs.readFileSync(file,'utf8'));
|
|
|
|
|
+ row.identityBasis='runtime_config';row.account.runtimeAgentId='actual-looking-id';fs.writeFileSync(file,JSON.stringify(row));
|
|
|
|
|
+ assert.throws(()=>readAgentProfileByKey(agent.key,opts.profileDir),{code:'INVALID_IDENTITY_FILE'});
|
|
|
|
|
+ const employee=registerParticipantProfile(employeeInput,opts).profile,employeeFile=path.join(opts.participantDir,employee.key.split(':')[1]+'.json'),person=JSON.parse(fs.readFileSync(employeeFile,'utf8'));
|
|
|
|
|
+ person.identityBasis='channel_context';person.account.tenantId='test-corp';person.account.accountId='test-user';fs.writeFileSync(employeeFile,JSON.stringify(person));
|
|
|
|
|
+ assert.throws(()=>readParticipantProfile(employee.key,opts.participantDir),{code:'INVALID_IDENTITY_FILE'});
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('任意数量同角色数字人可共存,旧档案迁入不覆盖原件,CLI仅输出摘要',t=>{
|
|
|
|
|
+ const root=directory(t),opts=dirs(root),first=registerAgentProfile(agentInput,opts).profile;
|
|
|
|
|
+ const second=registerAgentProfile({...agentInput,name:'另一个产品数字人'},opts).profile;assert.notEqual(first.key,second.key);
|
|
|
|
|
+ const legacy=path.join(root,'agent-self-workshop.json'),original=fs.readFileSync(path.join(opts.profileDir,first.key.split(':')[1]+'.json'));fs.writeFileSync(legacy,original,{mode:0o600});
|
|
|
|
|
+ const next={profileDir:path.join(root,'migrated')};assert.equal(adoptLegacyAgentProfile(legacy,next).changed,true);assert.equal(adoptLegacyAgentProfile(legacy,next).changed,false);assert.deepEqual(fs.readFileSync(legacy),original);
|
|
|
|
|
+ const input=path.join(root,'summary.json');fs.writeFileSync(input,JSON.stringify({...employeeInput,account:{...employeeInput.account,accountName:'private-account-name'}}));
|
|
|
|
|
+ const out=executeIdentity(['register-employee','--input',input],opts);assert.equal(out.databaseWrites,0);assert.equal(out.networkRequests,0);assert.equal(out.accountAuthenticated,false);assert.ok(!JSON.stringify(out).includes('private-account-name'));
|
|
|
|
|
+ const status=executeIdentity(['status'],opts);assert.equal(status.agents.length,2);assert.equal(status.participants.length,1);
|
|
|
|
|
+ assert.throws(()=>registerParticipantProfile({...employeeInput,reuseKey:out.profile.key,confirmNew:true},opts),{code:'INVALID_IDENTITY_VALUE'});
|
|
|
|
|
+ assert.throws(()=>readAgentProfileByKey('agent-registration:../../invalid',opts.profileDir),{code:'INVALID_AGENT_KEY'});
|
|
|
|
|
+});
|
|
|
|
|
+test('新员工先登记;稳定账号自动复用,无账号同名需人工确认',t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),first=registerParticipantProfile(employeeInput,opts);assert.equal(first.changed,true);assert.match(first.profile.key,/^participant-registration:/);
|
|
|
|
|
+ const candidates=findParticipantProfiles('新员工',opts);assert.equal(candidates.length,1);assert.ok(!JSON.stringify(candidates).includes('accountId'));
|
|
|
|
|
+ const pending=registerParticipantProfile(employeeInput,opts);assert.equal(pending.needsReview,true);assert.equal(pending.candidates[0].key,first.profile.key);
|
|
|
|
|
+ const reused=registerParticipantProfile({...employeeInput,reuseKey:first.profile.key},opts);assert.equal(reused.changed,false);assert.equal(reused.profile.key,first.profile.key);
|
|
|
|
|
+ const other=registerParticipantProfile({...employeeInput,confirmNew:true},opts);assert.notEqual(other.profile.key,first.profile.key);
|
|
|
|
|
+ const stable={...employeeInput,name:'有账号员工',identityBasis:'channel_context',account:{...employeeInput.account,tenantId:'corp-test',accountId:'wecom-user-7'}};
|
|
|
|
|
+ const a=registerParticipantProfile(stable,opts),b=registerParticipantProfile(stable,opts);assert.equal(a.profile.key,b.profile.key);assert.equal(b.changed,false);
|
|
|
|
|
+ assert.throws(()=>registerParticipantProfile({...stable,name:'不同显示名'},opts),{code:'PARTICIPANT_PROFILE_CONFLICT'});
|
|
|
|
|
+ assert.equal(checkParticipantProfile(first.profile.key,employeeInput,opts).matches,true);
|
|
|
|
|
+});
|
|
|
|
|
+test('workshop-start 必须引用登记的员工和 Agent 档案,并注入字段快照',async t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),agentRow=registerAgentProfile(agentInput,opts).profile,employeeRow=registerParticipantProfile(employeeInput,opts).profile;
|
|
|
|
|
+ const agent=readAgentProfileByKey(agentRow.key,opts.profileDir),participant=readParticipantProfile(employeeRow.key,opts.participantDir),calls=[];
|
|
|
|
|
+ const call=async(name,params)=>{calls.push({name,params});return name==='ksfGetIngestContract'?{workshop:{identitySchema:'ksf.agent-self.v1'}}:{sessionId:'session'};};
|
|
|
|
|
+ await assert.rejects(()=>runWorkshop('workshop-start',{participant:{profileKey:participant.key}},{requestId:'missing-agent',participantProfile:participant,call}),{code:'AGENT_PROFILE_REQUIRED'});
|
|
|
|
|
+ await assert.rejects(()=>runWorkshop('workshop-start',{}, {requestId:'missing-employee',agentProfile:agent,call}),{code:'PARTICIPANT_PROFILE_REQUIRED'});assert.equal(calls.length,0);
|
|
|
|
|
+ const input={workshopKey:'w',participant:{profileKey:participant.key},conversation:{key:'conversation-1',channel:'wecom'},groupId:'',dataMode:'sample'};
|
|
|
|
|
+ await runWorkshop('workshop-start',input,{requestId:'start',agentProfile:agent,participantProfile:participant,call});
|
|
|
|
|
+ const start=calls.find(x=>x.name==='ksfStartWorkshopSession').params;assert.deepEqual(start.agent,bindOwnAgent({},agent).agent);assert.deepEqual(start.participant,bindRegisteredParticipant({profileKey:participant.key},participant));assert.equal(start.dataMode,'sample');
|
|
|
|
|
+ calls.length=0;await assert.rejects(()=>runWorkshop('workshop-start',{...input,agent:{name:'别的数字人'}},{requestId:'fake',agentProfile:agent,participantProfile:participant,call}),{code:'AGENT_IDENTITY_MISMATCH'});assert.equal(calls.length,0);
|
|
|
|
|
+ await assert.rejects(()=>runWorkshop('workshop-start',input,{requestId:'ops',agentProfile:{...agent,role:'operations'},participantProfile:participant,call}),{code:'AGENT_ROLE_MISMATCH'});assert.equal(calls.length,0);
|
|
|
|
|
+});
|
|
|
|
|
+test('旧服务不支持身份版本时会话写入前停止',async t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),agentRow=registerAgentProfile(agentInput,opts).profile,employeeRow=registerParticipantProfile(employeeInput,opts).profile,calls=[];
|
|
|
|
|
+ const agent=readAgentProfileByKey(agentRow.key,opts.profileDir),employee=readParticipantProfile(employeeRow.key,opts.participantDir);
|
|
|
|
|
+ await assert.rejects(()=>runWorkshop('workshop-start',{participant:{profileKey:employee.key}},{requestId:'old-service',agentProfile:agent,participantProfile:employee,call:async name=>{calls.push(name);return{workshop:{version:'old'}};}}),{code:'IDENTITY_CAPABILITY_UNAVAILABLE'});assert.deepEqual(calls,['ksfGetIngestContract']);
|
|
|
|
|
+ await assert.rejects(()=>runWorkshop('workshop-start',{},{requestId:'no-agent',participantProfile:employee,call:async()=>{throw Error('must not call');}}),{code:'AGENT_PROFILE_REQUIRED'});
|
|
|
|
|
+});
|
|
|
|
|
+test('只读回读脱敏账号与会话键,公开producer不携带账号值',async t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),agentRow=registerAgentProfile({...agentInput,account:{...agentInput.account,accountId:'private-account'}},opts).profile,agent=readAgentProfileByKey(agentRow.key,opts.profileDir);
|
|
|
|
|
+ const result=await runWorkshop('workshop-read',{className:'KfArtifact',objectId:'artifact'},{requestId:'read',call:async(n,params)=>{assert.equal(n,'ksfGetRecord');assert.equal(params.objectId,'artifact');return{record:{title:'报告',agentName:agent.name,userRef:{objectId:'private-user'},meta:{ownerId:'private-service',attribution:{agentName:agent.name,agentAccount:{accountId:'private-account'},participantAccount:{accountId:'private-person'},conversationKey:'private-chat'}}}};}});
|
|
|
|
|
+ assert.equal(result.record.title,'报告');assert.ok(!JSON.stringify(result).includes('private-'));assert.deepEqual(redactIdentity({producer:{agent:{name:agent.name,key:agent.key,identityBasis:agent.identityBasis,account:{accountId:'private-account'}}}}).producer.agent,{name:agent.name,key:agent.key,identityBasis:agent.identityBasis});
|
|
|
|
|
+ assert.equal(listIdentityProfiles(opts).agents.length,1);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const channelContext={version:'ksf.wecom-aibot-context.v1',botId:'private-bot-one',senderId:'private-sender-one',chatType:'single',chatId:null,messageId:'message-one'};
|
|
|
|
|
+function channelAgent(opts,botId=channelContext.botId){const a=registerAgentProfile({...agentInput,name:botId==='private-bot-one'?'王刚001':'运维001',identityBasis:'runtime_config',account:{platform:'wecom',accountId:botId}},opts).profile;return readAgentProfileByKey(a.key,opts.profileDir);}
|
|
|
|
|
+
|
|
|
|
|
+test('aibot 无企业ID或姓名也能按发送者自动登记,单聊换群不改变员工键',t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),agentProfile=channelAgent(opts);
|
|
|
|
|
+ const first=registerChannelParticipant(channelContext,{...opts,agentProfile});assert.equal(first.changed,true);assert.equal(first.needsNameBinding,true);assert.match(first.profile.name,/^员工待绑定·/);
|
|
|
|
|
+ const row=readParticipantProfile(first.profile.key,opts.participantDir);assert.equal(row.account.tenantId,null);assert.equal(row.userId,null);assert.equal(row.account.accountId,channelContext.senderId);assert.equal(row.account.scopeId,channelContext.botId);assert.equal(row.account.identifierType,'wecom_aibot_sender');
|
|
|
|
|
+ const filename=path.join(opts.participantDir,row.key.split(':')[1]+'.json'),before=fs.readFileSync(filename);
|
|
|
|
|
+ const again=registerChannelParticipant({...channelContext,chatType:'group',chatId:'private-group',messageId:'message-two'},{...opts,agentProfile});assert.equal(again.changed,false);assert.equal(again.profile.key,first.profile.key);assert.deepEqual(fs.readFileSync(filename),before);
|
|
|
|
|
+ assert.equal(first.channelContextOriginVerified,false);assert.ok(!JSON.stringify(first).includes('private-'));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('同一群里不同发送者分开;跨机器人标识不擅自合并',t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),agentProfile=channelAgent(opts),a=registerChannelParticipant({...channelContext,chatType:'group',chatId:'private-group'},{...opts,agentProfile});
|
|
|
|
|
+ const b=registerChannelParticipant({...channelContext,senderId:'private-sender-two',chatType:'group',chatId:'private-group'},{...opts,agentProfile});assert.notEqual(a.profile.key,b.profile.key);
|
|
|
|
|
+ const other=channelAgent(opts,'private-bot-two'),c=registerChannelParticipant({...channelContext,botId:'private-bot-two'},{...opts,agentProfile:other});assert.notEqual(a.profile.key,c.profile.key);assert.equal(listIdentityProfiles(opts).participants.length,3);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('aibot 缺发送者/机器人/群会话ID或机器人不匹配在登记前停止',t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),agentProfile=channelAgent(opts);
|
|
|
|
|
+ for(const bad of [{...channelContext,senderId:''},{...channelContext,botId:''},{...channelContext,chatType:'group',chatId:null},{...channelContext,displayName:'猜出的姓名'},{...channelContext,rawText:'不得从正文提取身份'}])assert.throws(()=>registerChannelParticipant(bad,{...opts,agentProfile}));
|
|
|
|
|
+ assert.throws(()=>registerChannelParticipant({...channelContext,botId:'different-bot'},{...opts,agentProfile}),{code:'CHANNEL_AGENT_MISMATCH'});
|
|
|
|
|
+ assert.equal(listIdentityProfiles(opts).participants.length,0);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('aibot 旧文件 CLI 已关闭;旧合同与错机器人阻止会话',async t=>{
|
|
|
|
|
+ const root=directory(t),opts=dirs(root),agentProfile=channelAgent(opts),file=path.join(root,'inbound.json');fs.writeFileSync(file,JSON.stringify(channelContext),{mode:0o600});
|
|
|
|
|
+ for(const argv of [[],['--context',file],['--agent-profile',agentProfile.key,'--context',file],['--message-id','made-up']])assert.throws(()=>executeIdentity(['register-channel-employee',...argv],opts),{code:'TRUSTED_IDENTITY_TOOL_REQUIRED'});
|
|
|
|
|
+ assert.equal(listIdentityProfiles(opts).participants.length,0);
|
|
|
|
|
+ const result=registerBridgeIdentity({version:'ksf.current-employee-registration.v1',agentProfileKey:agentProfile.key,identity:channelContext},opts);assert.ok(!JSON.stringify(result).includes('private-'));assert.equal(result.databaseWrites,0);assert.equal(result.accountAuthenticated,false);
|
|
|
|
|
+ const participantProfile=readParticipantProfile(result.profile.key,opts.participantDir),task={participant:{profileKey:participantProfile.key},conversation:{key:'private-chat',channel:'wecom'}},calls=[];
|
|
|
|
|
+ const options={requestId:'channel-start',agentProfile,participantProfile,call:async name=>{calls.push(name);return{workshop:{identitySchema:'ksf.agent-self.v1'}};}};
|
|
|
|
|
+ await assert.rejects(()=>runWorkshop('workshop-start',task,options),{code:'CHANNEL_IDENTITY_CAPABILITY_UNAVAILABLE'});assert.deepEqual(calls,['ksfGetIngestContract']);
|
|
|
|
|
+ const call=async name=>{calls.push(name);return{workshop:{identitySchema:'ksf.agent-self.v1',channelIdentitySchema:'ksf.wecom-aibot-sender.v1'}};};
|
|
|
|
|
+ calls.length=0;await assert.rejects(()=>runWorkshop('workshop-start',task,{...options,call,agentProfile:channelAgent(opts,'private-bot-two')}),{code:'CHANNEL_AGENT_MISMATCH'});assert.deepEqual(calls,['ksfGetIngestContract']);
|
|
|
|
|
+ assert.deepEqual(redactIdentity({scopeId:'private-bot',botId:'private-bot',senderId:'private-user',name:'显示标签'}),{name:'显示标签'});
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('通用员工 JSON 不能替代通道工具;内部登记复用规范键且拒绝错实例与运维角色',t=>{
|
|
|
|
|
+ const root=directory(t),opts=dirs(root),agentProfile=channelAgent(opts),input=path.join(root,'input.json');
|
|
|
|
|
+ for(const value of [
|
|
|
|
|
+ {...employeeInput,identityBasis:'channel_context',account:{platform:'wecom',tenantId:'corp',accountId:'sender'}},
|
|
|
|
|
+ {...employeeInput,account:{platform:'wecom',identifierType:'wecom_aibot_sender',scopeId:'bot',accountId:'sender'}}
|
|
|
|
|
+ ]){fs.writeFileSync(input,JSON.stringify(value));assert.throws(()=>executeIdentity(['register-employee','--input',input],opts),{code:'TRUSTED_IDENTITY_TOOL_REQUIRED'});}
|
|
|
|
|
+ assert.equal(listIdentityProfiles(opts).participants.length,0);
|
|
|
|
|
+ const request={version:'ksf.current-employee-registration.v1',agentProfileKey:agentProfile.key,identity:channelContext};
|
|
|
|
|
+ const a=registerBridgeIdentity(request,opts),b=registerBridgeIdentity({...request,identity:{...channelContext,messageId:'another-message',chatType:'group',chatId:'group'}},opts);
|
|
|
|
|
+ assert.equal(a.profile.key,b.profile.key);assert.equal(a.changed,true);assert.equal(b.changed,false);
|
|
|
|
|
+ assert.throws(()=>registerBridgeIdentity({...request,messageId:'extra-field'},opts),{code:'INVALID_BRIDGE_REQUEST'});
|
|
|
|
|
+ assert.throws(()=>registerBridgeIdentity({...request,identity:{...channelContext,botId:'other-bot'}},opts),{code:'CHANNEL_AGENT_MISMATCH'});
|
|
|
|
|
+ const ops=registerAgentProfile({...agentInput,name:'运维001',role:'operations',account:{platform:'wecom',accountId:'operations-bot'}},opts).profile;
|
|
|
|
|
+ assert.throws(()=>registerBridgeIdentity({...request,agentProfileKey:ops.key},opts),{code:'AGENT_ROLE_MISMATCH'});
|
|
|
|
|
+ assert.equal(listIdentityProfiles(opts).participants.length,1);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('上传实名与待同步回执均保留;成功保存不因实名状态再次调用',async t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),agentProfile=channelAgent(opts);
|
|
|
|
|
+ const verified={employee:{key:'employee:'+'a'.repeat(64),name:'测试员工',departmentNames:['测试产品部'],source:'wecom_directory',nameVerified:true,verifiedAt:'2026-11-03T00:00:00.000Z'},employeeIdentity:{automatic:true,status:'verified'}};
|
|
|
|
|
+ const pending={employee:{key:'channel-employee:'+'b'.repeat(64),name:null,source:'wecom_sender',nameVerified:false},employeeIdentity:{automatic:true,status:'pending',reason:'WECOM_DIRECTORY_NOT_CONFIGURED'}};
|
|
|
|
|
+ for(const receipt of [verified,pending,{}]){
|
|
|
|
|
+ const calls=[];
|
|
|
|
|
+ const result=await runWorkshop('workshop-save',{sessionId:'original-session',taskKey:'original-task',version:1},{requestId:'original-save',agentProfile,call:async(name,params)=>{
|
|
|
|
|
+ calls.push({name,params});
|
|
|
|
|
+ if(name==='ksfGetRecord')return{record:{meta:{attribution:{agentKey:agentProfile.key,agentName:agentProfile.name,agentRuntimePlatform:agentProfile.runtimePlatform,agentChannel:agentProfile.channel}}}};
|
|
|
|
|
+ assert.equal(name,'ksfSaveWorkshopTask');assert.equal(params.requestId,'original-save');
|
|
|
|
|
+ assert.equal(params.sessionId,'original-session');assert.equal(params.taskKey,'original-task');
|
|
|
|
|
+ return{artifactId:'saved-artifact',...receipt,meta:{attribution:{participantAccount:{accountId:'private-sender'},agentAccount:{accountId:'private-bot'}}}};
|
|
|
|
|
+ }});
|
|
|
|
|
+ assert.equal(result.artifactId,'saved-artifact');assert.deepEqual(result.employee,receipt.employee);assert.deepEqual(result.employeeIdentity,receipt.employeeIdentity);
|
|
|
|
|
+ assert.deepEqual(calls.map(x=>x.name),['ksfGetRecord','ksfSaveWorkshopTask']);assert.ok(!JSON.stringify(result).includes('private-'));
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('通讯录查询失败透传并停止,Skill运行库不换键重试保存',async t=>{
|
|
|
|
|
+ const opts=dirs(directory(t)),agentProfile=channelAgent(opts),calls=[];
|
|
|
|
|
+ await assert.rejects(()=>runWorkshop('workshop-save',{sessionId:'original-session',taskKey:'original-task'},{requestId:'original-save',agentProfile,call:async(name,params)=>{
|
|
|
|
|
+ calls.push({name,params});
|
|
|
|
|
+ if(name==='ksfGetRecord')return{record:{meta:{attribution:{agentKey:agentProfile.key,agentName:agentProfile.name,agentRuntimePlatform:agentProfile.runtimePlatform,agentChannel:agentProfile.channel}}}};
|
|
|
|
|
+ throw Object.assign(Error('fixture failure'),{code:'WECOM_SENDER_MAPPING_REQUIRED'});
|
|
|
|
|
+ }}),{code:'WECOM_SENDER_MAPPING_REQUIRED'});
|
|
|
|
|
+ assert.deepEqual(calls.map(x=>x.name),['ksfGetRecord','ksfSaveWorkshopTask']);assert.equal(calls[1].params.requestId,'original-save');
|
|
|
|
|
+});
|