workshop-account.test.js 4.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  1. import test from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import path from 'node:path';
  4. import {runAccountBinding} from '../skills/ksf-workshop-runtime/scripts/ksf-account.mjs';
  5. import {parseArgs} from '../skills/ksf-workshop-runtime/scripts/ksf-secure.mjs';
  6. import {redactIdentity} from '../skills/ksf-workshop-runtime/scripts/ksf-identity.mjs';
  7. import {runWorkshop} from '../skills/ksf-workshop-runtime/scripts/ksf-workshop.mjs';
  8. const profile={key:'participant-registration:1ab680ff-4ad4-4377-9333-3b2f23e26bb7',account:{platform:'fmode',accountName:'guopeng',accountId:null}};
  9. const task={participant:{profileKey:profile.key},username:'guopeng',displayName:'果鹏'};
  10. const opts={requestId:'first-link',agentProfile:{role:'workshop'},participantProfile:profile};
  11. test('首次绑定命令只传当前档案和自报称呼账号,不收密码或冒充平台确认',async()=>{
  12. let called=0;const call=async(name,p)=>{called++;assert.equal(name,'ksfWorkshopAccount');assert.equal(p.action,'prepare');assert.equal(p.participantProfileKey,profile.key);assert.ok(!('password'in p));return{status:'awaiting_account_confirmation'};};
  13. const r=await runAccountBinding('account-prepare',task,{...opts,call});assert.equal(r.status,'awaiting_account_confirmation');assert.equal(called,1);
  14. await assert.rejects(()=>runAccountBinding('account-confirm',task,{...opts,call}),{code:'INVALID_INPUT'});assert.equal(called,1);
  15. });
  16. test('档案不匹配、冒用其他账号或通道字段写入前拒绝',async()=>{
  17. const call=()=>{assert.fail('must stop before network');};
  18. for(const t of [{...task,username:'liuna'},{...task,participant:{profileKey:'another'}},{...task,password:'forbidden'}])await assert.rejects(()=>runAccountBinding('account-prepare',t,{...opts,call}));
  19. const status=await runAccountBinding('account-status',{participant:task.participant},{...opts,call:async()=>({status:'bound',memberKey:'verified-key'})});assert.equal(status.memberKey,'verified-key');
  20. });
  21. test('新增受控命令沿用加密凭据入口,要求Agent及请求编号',()=>{
  22. const flags=['--input',path.resolve('work/task.json'),'--request-id','link-test','--agent-profile','agent-account:'+'a'.repeat(64)];
  23. assert.equal(parseArgs(['account-prepare',...flags]).command,'account-prepare');
  24. assert.equal(parseArgs(['account-status',...flags]).command,'account-status');
  25. assert.throws(()=>parseArgs(['account-prepare','--input',path.resolve('work/task.json'),'--request-id','link-test']),{code:'AGENT_PROFILE_REQUIRED'});
  26. });
  27. test('报告回读移除新增私有绑定快照和平台编号,只保留公开memberKey',()=>{
  28. const result=redactIdentity({memberKey:'public-member',meta:{attribution:{participantKey:'old-participant',accountBinding:{platformUserId:'private-user'},accountProfileKey:'private-profile',platformUserId:'private-user'}}});
  29. assert.deepEqual(result,{memberKey:'public-member',meta:{attribution:{participantKey:'old-participant'}}});
  30. });
  31. const bindingContract={schema:'ksf.workshop-account-binding.v1',enabled:true,requiredFor:['fmode'],function:'ksfWorkshopAccount',firstUse:'confirm-display-name-and-account',proof:'platform-session',canonicalMember:'platform-account',oneMemberPerAccount:true,unboundSubmission:'reject',historicalAttributionPreserved:true};
  32. test('真实workshop-contract路径保留公开账号绑定契约,不把正常脱敏当成服务端未部署',async()=>{
  33. const contract={workshop:{version:'ksf.workshop.v1',accountBinding:bindingContract}};
  34. const result=await runWorkshop('workshop-contract',{}, {requestId:'contract-regression',call:async(name)=>{assert.equal(name,'ksfGetIngestContract');return structuredClone(contract);}});
  35. assert.deepEqual(result.workshop.accountBinding,bindingContract);
  36. });
  37. test('会话创建回执保留安全绑定摘要,私有归属快照即使伪装成公开契约仍不透出',()=>{
  38. const summary={schema:'ksf.workshop-account-binding.v1',status:'bound',memberKey:'platform-account:'+'a'.repeat(64),username:'guopeng',displayName:'果鹏',companyId:'FKieRbIoGt',verifiedAt:'2026-11-12T06:00:00.000Z',source:'platform_session'};
  39. const input={sessionId:'session-one',accountBinding:{...summary,platformUserId:'private-user'},record:{meta:{attribution:{accountBinding:{...bindingContract,platformUserId:'private-user'},accountProfileKey:'private-profile'}}}};
  40. const result=redactIdentity(input);
  41. assert.deepEqual(result.accountBinding,summary);assert.equal(result.record.meta.attribution.accountBinding,undefined);
  42. assert.ok(!JSON.stringify(result).includes('private-user'));assert.ok(!JSON.stringify(result).includes('private-profile'));
  43. });