fmode-client.test.mjs 2.8 KB

123456789101112131415161718192021
  1. import test from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import crypto from 'node:crypto';
  4. import {createFmodeClient} from '../client/fmode-client.mjs';
  5. const response=(body,status=200)=>({status,ok:status===200,json:async()=>body});
  6. test('独立适配使用动态入口并补齐明确声明的报告元数据,不修改 r7 入参',async()=>{
  7. let request;const c=createFmodeClient({serviceToken:'fixture-only',reportMetadata:{stageCode:'S2'},fetchImpl:async(url,opts)=>{request={url,opts};return response({result:{ok:true,errors:[],data:{jobId:'job'}}});}});
  8. const p={items:[{className:'KfArtifact',data:{title:'报告'}}]};await c.invoke('ksfSubmitImport',p);const sent=JSON.parse(request.opts.body);
  9. assert.equal(sent.path,'/ksfSubmitImport');assert.equal(sent.params.items[0].data.stageCode,'S2');assert.equal(sent.params.items[0].data.contentSchema,'ksf.html-outline.v1');assert.equal(p.items[0].data.stageCode,undefined);assert.equal(request.opts.headers['X-Ksf-Service-Token'],'fixture-only');assert(!Object.keys(request.opts.headers).some(k=>k.toLowerCase().includes('master')));
  10. });
  11. test('业务 ok:false 和非空 errors 都不能继续',async()=>{
  12. for(const result of [{ok:false,errors:[],data:{jobId:'bad'}},{ok:true,errors:[{code:'DENIED',message:'拒绝'}],data:{}}]){const c=createFmodeClient({fetchImpl:async()=>response({result})});await assert.rejects(()=>c.invoke('ksfSubmitImport',{}));}
  13. });
  14. test('传输必须得到真实完整字节回执,不补造字节数或 hash',async()=>{
  15. const bytes=Buffer.from('真实报告'),sha256=crypto.createHash('sha256').update(bytes).digest('hex');
  16. const upload={url:'https://kangshifu.fmode.cn/api/functions?path=%2Fksf%2Fupload&uploadId=fixture',method:'POST',headers:{'X-Ksf-Upload-Token':'fixture'}};
  17. let sent;const c=createFmodeClient({fetchImpl:async(url,opts)=>{sent=opts;return response({ok:true,sizeBytes:bytes.length,sha256,fileId:'file1'});}});assert.equal((await c.transfer(upload,bytes)).bytes,bytes.length);assert.equal(sent.headers['Content-Type'],'application/octet-stream');assert.equal(sent.body,bytes);
  18. const missing=createFmodeClient({fetchImpl:async()=>response({ok:true})});await assert.rejects(()=>missing.transfer(upload,bytes),/不完整/);
  19. });
  20. test('跨域上传地址在发请求前拒绝',async()=>{let calls=0;const c=createFmodeClient({fetchImpl:async()=>{calls++;}});await assert.rejects(()=>c.transfer({url:'https://example.test/upload',method:'POST'},Buffer.from('x')));assert.equal(calls,0);});
  21. test('未获得真实员工事件前不生成确认凭据',async()=>{let calls=0;const c=createFmodeClient({fetchImpl:async()=>{calls++;return response({result:{ok:true,errors:[],data:{status:'awaiting_employee'}}});}});await assert.rejects(()=>c.confirmation('preview',{}),/等待员工/);assert.equal(calls,1);});