| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 |
- import test from 'node:test';
- import assert from 'node:assert/strict';
- import {loadPublicBoard,publicBoardAction} from '../src/services/workshop-data.js';
- import {buildModel} from '../src/core/kfs/build.js';
- import {runWorkshop,createWorkshopCaller} from '../skills/ksf-workshop-runtime/scripts/ksf-workshop.mjs';
- import {parseArgs,executeSecure} from '../skills/ksf-workshop-runtime/scripts/ksf-secure.mjs';
- import fs from 'node:fs';
- import path from 'node:path';
- import crypto from 'node:crypto';
- const blank=()=>Object.fromEntries(['project','artifact','insight','source','session','opportunity'].map(k=>[k,[]]));
- test('公开操作只发一次,不带身份密钥;业务失败与未知写结果均不重试',async()=>{
- const base={url:'/api/functions',appId:'public-id',name:'ksfSetPublicOpportunityStatus',params:{requestId:'same-request'}};
- let calls=0;
- const fetchImpl=async(_url,options)=>{calls++;assert.deepEqual(Object.keys(options.headers).sort(),['Content-Type','X-Parse-Application-Id']);return{ok:true,json:async()=>({result:{ok:false,errors:[{code:'VERSION_CONFLICT',message:'请刷新'}]}})};};
- await assert.rejects(()=>publicBoardAction({...base,fetchImpl}),{code:'VERSION_CONFLICT',requestId:'same-request'});assert.equal(calls,1);
- calls=0;await assert.rejects(()=>publicBoardAction({...base,fetchImpl:async()=>{calls++;throw Error('connection closed');}}),/connection closed/);assert.equal(calls,1);
- });
- const fixtureAgent={key:'agent-registration:00000000-0000-4000-8000-000000000001',name:'测试Agent',role:'workshop',identityBasis:'operator_registration',runtimePlatform:'test',channel:'technical-test',registeredAt:'2026-11-02T01:00:00.000Z',account:{platform:'test'}};
- const fixtureParticipant={key:'participant-registration:00000000-0000-4000-8000-000000000001',name:'测试员工',identityBasis:'participant_registration',account:{platform:'test'}};
- test('公开查询无需身份凭据,分页合并,不用 REST count',async()=>{const requests=[];const raw=await loadPublicBoard({appId:'public-id',fetchImpl:async(url,options)=>{
- requests.push({url,options});const params=JSON.parse(options.body).params;
- return{ok:true,json:async()=>({result:{ok:true,data:{...blank(),artifact:[{objectId:params.cursor?'a2':'a1'}],nextCursor:params.cursor?null:'next',asOf:'now'}}})};
- }});assert.deepEqual(raw.artifact.map(x=>x.objectId),['a1','a2']);assert.equal(requests.length,2);assert.ok(requests.every(x=>!Object.keys(x.options.headers).some(k=>/token|master|authorization/i.test(k))));});
- test('新版阶段/项目 Pointer 与 Agent 归属进入既有模型,显式卡住可见',()=>{const raw={...blank(),project:[{objectId:'p',title:'项目',stageCode:'S4',ownerName:'彭智康',uploadedByKey:'participant:one',progressState:'blocked'}],artifact:[{objectId:'a',title:'报告',stageCode:'S3',projectRef:{objectId:'p'},ownerName:'彭智康',uploadedByKey:'participant:one',agentName:'王刚001',agentId:'name:王刚001',uploadMethod:'session'}],session:[{objectId:'s',studentName:'彭智康',stageCode:'S4',groupId:'G1',meta:{participantKey:'participant:one'}}]};const db=buildModel(raw);assert.equal(db.artifacts[0].stageObj.code,'S3');assert.equal(db.artifacts[0].projectId,'p');assert.equal(db.artifacts[0].upload.agentName,'王刚001');assert.equal(db.projects[0].stage.code,'S4');assert.deepEqual(db.projects[0].artifactIds,['a']);assert.equal(db.memberByName['彭智康'].stuck,true);});
- test('同名学员不同稳定键不会合并为一个人',()=>{const raw={...blank(),session:[{objectId:'s1',studentName:'同名',groupId:'G1',meta:{participantKey:'participant:one'}},{objectId:'s2',studentName:'同名',groupId:'G2',meta:{participantKey:'participant:two'}}]};const db=buildModel(raw);assert.equal(db.members.length,2);assert.ok(db.memberByName['同名(G1)']);assert.ok(db.memberByName['同名(G2)']);});
- test('同组同名学员仍分开,并将项目归给正确参与人',()=>{const raw={...blank(),session:[{objectId:'s1',studentName:'同名',groupId:'G1',meta:{participantKey:'participant:00000001'}},{objectId:'s2',studentName:'同名',groupId:'G1',meta:{participantKey:'participant:00000002'}}],project:[{objectId:'p',ownerName:'同名',uploadedByKey:'participant:00000002'}]};const db=buildModel(raw);assert.equal(db.members.length,2);assert.deepEqual(db.memberByName['同名(G1 · 00000002)'].projectIds,['p']);assert.equal(db.memberByName['同名(G1 · 00000001)'].projectIds.length,0);});
- test('Skill 调用固定业务函数,不接受任意命令;请求沿用指定稳定键',async()=>{const calls=[];const result=await runWorkshop('workshop-start',{workshopKey:'w',participant:{profileKey:fixtureParticipant.key}},{requestId:'r',agentProfile:fixtureAgent,participantProfile:fixtureParticipant,call:async(n,p)=>{calls.push({n,p});return n==='ksfGetIngestContract'?{workshop:{identitySchema:'ksf.agent-self.v1'}}:{sessionId:'s'};}});assert.equal(result.sessionId,'s');assert.equal(calls[1].n,'ksfStartWorkshopSession');assert.equal(calls[1].p.requestId,'r');await assert.rejects(()=>runWorkshop('delete-everything',{},{}),{code:'INVALID_INPUT'});await assert.rejects(()=>runWorkshop('project-create',{requestId:'other'},{requestId:'r'}),{code:'BAD_REQUEST_ID'});});
- test('结构化产品/原料导入解析真实依赖并核对逐项回执',async()=>{const calls=[];let index=0;const result=await runWorkshop('workshop-import',{source:{title:'实际资料',sourceKind:'dataset',dataMode:'manual',rawText:'原始资料'},records:[{itemKey:'category',className:'KfCategory',externalKey:'cat',data:{dataMode:'manual'}},{itemKey:'product',className:'KfProduct',externalKey:'prod',dependsOn:['category'],data:{dataMode:'manual',categoryRef:{$item:'category'}}}]},{requestId:'import',agentProfile:fixtureAgent,call:async(n,p)=>{calls.push({n,p});if(n==='ksfGetIngestContract')return{deduplication:{schema:'ksf.ingest-dedup.v1'},schemaVersion:'ksf.v0.7',allowedClasses:[{className:'KfCategory',fields:[]},{className:'KfProduct',fields:[{name:'sourceRef'}]}]};if(n==='ksfCheckDuplicates')return{schema:'ksf.ingest-dedup.v1',readOnly:true,items:[]};if(n==='ksfRegisterSource')return{sourceId:'source'};if(n==='ksfValidateImport')return{valid:true};if(n==='ksfSubmitImport')return{jobId:'job-'+(++index)};return{job:{status:'succeeded'},items:[{itemKey:index===1?'category':'product',objectId:'object-'+index,status:'succeeded',snapshotState:'available',operation:'created'}]};}});assert.equal(result.items.length,2);const sent=calls.filter(x=>x.n==='ksfSubmitImport')[1].p.items[0].data;assert.deepEqual(sent.categoryRef,{__type:'Pointer',className:'KfCategory',objectId:'object-1'});assert.equal(sent.sourceRef.objectId,'source');});
- test('网络结果不明确时不自动重试;API 业务错误阻止成功',async()=>{let count=0;const c=createWorkshopCaller({token:'fixture-only',fetchImpl:async()=>{count++;throw Error('timeout');}});await assert.rejects(()=>c('ksfStartWorkshopSession',{}),{code:'REMOTE_REQUEST_UNCERTAIN'});assert.equal(count,1);const failed=createWorkshopCaller({token:'fixture-only',fetchImpl:async()=>({ok:true,json:async()=>({result:{ok:false,errors:[{code:'FORBIDDEN'}]}})})});await assert.rejects(()=>failed('x',{}),{code:'FORBIDDEN'});});
- test('查重可独立只读调用,无查重合同导入在任何写入前停',async()=>{
- const calls=[];const input={source:{rawText:'实际资料',sourceKind:'dataset',dataMode:'sample'},records:[{itemKey:'m',className:'KfMaterial',externalKey:'m',data:{dataMode:'sample'}}]};
- const result=await runWorkshop('workshop-dedup',input,{requestId:'dedup-only',call:async(name,p)=>{calls.push(name);assert.equal(name,'ksfCheckDuplicates');return{schema:'ksf.ingest-dedup.v1',readOnly:true,items:[]};}});assert.equal(result.readOnly,true);
- assert.equal(parseArgs(['workshop-dedup','--input',path.resolve('work/task.json'),'--request-id','dedup-only']).command,'workshop-dedup');
- calls.length=0;await assert.rejects(()=>runWorkshop('workshop-import',input,{requestId:'no-contract',agentProfile:fixtureAgent,call:async name=>{calls.push(name);return{allowedClasses:[{className:'KfMaterial',fields:[]}]};}}),{code:'DEDUP_CAPABILITY_UNAVAILABLE'});assert.deepEqual(calls,['ksfGetIngestContract']);
- });
- test('导入中途回读失败,保留来源、已成功对象及当前任务编号',async()=>{let n=0;const args={source:{dataMode:'sample',rawText:'技术样例'},records:['one','two'].map(itemKey=>({itemKey,className:'KfMaterial',externalKey:itemKey,data:{dataMode:'sample'}}))};await assert.rejects(()=>runWorkshop('workshop-import',args,{requestId:'partial',agentProfile:fixtureAgent,call:async(name,p)=>{
- if(name==='ksfGetIngestContract')return{deduplication:{schema:'ksf.ingest-dedup.v1'},allowedClasses:[{className:'KfMaterial',fields:[]}]};if(name==='ksfCheckDuplicates')return{schema:'ksf.ingest-dedup.v1',readOnly:true,items:[]};if(name==='ksfRegisterSource')return{sourceId:'s'};if(name==='ksfValidateImport')return{valid:true};if(name==='ksfSubmitImport')return{jobId:'job-'+(++n)};if(n===2)throw Object.assign(Error('uncertain'),{code:'REMOTE_REQUEST_UNCERTAIN'});return{job:{status:'succeeded'},items:[{itemKey:'one',objectId:'o1',status:'succeeded',snapshotState:'available'}]};
- }}),e=>{assert.equal(e.code,'REMOTE_REQUEST_UNCERTAIN');assert.equal(e.partial.sourceId,'s');assert.equal(e.partial.items[0].objectId,'o1');assert.equal(e.partial.active.jobId,'job-2');assert.deepEqual(e.partial.pendingItemKeys,['two']);return true;});assert.equal(n,2);});
- test('加密入口新命令仍逐次解密并核对新脚本指纹,保留旧入口',async()=>{
- const dir=path.resolve('skills/ksf-workshop-runtime'),sha=x=>crypto.createHash('sha256').update(x).digest('hex');
- const envelope=Buffer.from(JSON.stringify({header:{recipientHost:'test-host',recipientDirectory:'/opt/data/key',publicKeySha256:'a'.repeat(64)}})),decrypt=Buffer.from('test decrypt module');
- const binding={version:'ksf.encrypted-entry.v1',purpose:'KSF-credential-receive-20261031',host:'test-host',envelopeFile:'/opt/data/envelope',envelopeSha256:sha(envelope),decryptModule:'/opt/data/decrypt',decryptModuleSha256:sha(decrypt),keyDirectory:'/opt/data/key',publicKeySha256:'a'.repeat(64)};
- const readBytes=file=>file.endsWith('credential-binding.json')?Buffer.from(JSON.stringify(binding)):file===binding.envelopeFile?envelope:file===binding.decryptModule?decrypt:fs.readFileSync(file);
- const result=await executeSecure(['project-status','--input',path.resolve('work/task.json'),'--request-id','test-r'],{skillDir:dir,hostname:'test-host',readBytes,readPrivateKey:()=>({}),importDecrypt:async()=>({decryptEnvelope:()=>({env:{KSF_SERVICE_TOKEN:'fixture-only'},expiresAt:'2099-01-01'})}),spawn:(_exe,args,options)=>{
- assert.equal(options.env.KSF_SERVICE_TOKEN,'fixture-only');assert.ok(args[0].endsWith('ksf-workshop.mjs'));return{status:0,stdout:JSON.stringify({skill:'ksf-workshop-runtime',entry:args[0],entrySha256:sha(fs.readFileSync(args[0])),command:'project-status',requestId:'test-r',invocationId:crypto.randomUUID(),ok:true}),stderr:''};
- }});assert.equal(result.credentialWrittenToDisk,false);assert.equal(result.gatewayRestarted,false);assert.equal(result.ok,true);assert.equal(parseArgs(['demo']).command,'demo');assert.equal(sha(fs.readFileSync(dir+'/scripts/ksf.mjs')),'7ef128f7d022bfbc15c0ec813bfd87995c6297c6efe407a563c840f014aba477');
- });
|