| 1234567891011121314151617 |
- 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 crypto from 'node:crypto';
- import {deployDedup} from '../scripts/deploy-dedup.mjs';
- const sha=x=>crypto.createHash('sha256').update(x).digest('hex');
- function fixture(t){
- const root=fs.mkdtempSync(path.join(os.tmpdir(),'ksf-dedup-deploy-'));t.after(()=>{assert.equal(path.dirname(root),fs.realpathSync(os.tmpdir()));assert.ok(path.basename(root).startsWith('ksf-dedup-deploy-'));fs.rmSync(root,{recursive:true});});fs.mkdirSync(path.join(root,'dist'));
- const targets={ksfRegisterSource:'czT35BZZlx',ksfValidateImport:'ra9OWsmbtC',ksfSubmitImport:'gPjyoZbHhU',ksfGetIngestContract:'dWSrHp4FfQ',ksfCheckDuplicates:'JDNsVUCXSo'},db={},writes=[];
- const manifest={version:'ksf.dedup-deployment.v1',appId:'FKieRbIoGt_main',functions:Object.entries(targets).map(([name,objectId],i)=>{const before='function old'+i+'(){}',code='function next'+i+'(){}',file='dist/'+name+'.js';if(objectId)db[objectId]={name,objectId,path:'/'+name,code:before};fs.writeFileSync(path.join(root,file),code);return{name,objectId,path:'/'+name,file,sha256:sha(code),beforeSha256:objectId?[sha(before)]:[]};})};fs.writeFileSync(path.join(root,'dist/dedup-manifest.json'),JSON.stringify(manifest));
- const request=async(method,route,body)=>{if(route==='/schemas')return{results:[]};if(method==='GET'&&route.startsWith('/classes/Function?')){const where=JSON.parse(new URLSearchParams(route.split('?')[1]).get('where'));return{results:Object.values(db).filter(r=>where.$or.some(x=>Object.entries(x).every(([k,v])=>r[k]===v))).map(r=>structuredClone(r))};}const id=route.split('/').at(-1);if(method==='GET')return structuredClone(db[id]);writes.push({method,id});if(method==='POST'){db.newCheck={...body,objectId:'newCheck'};return{objectId:'newCheck'};}if(method==='PUT')db[id].code=body.code;if(method==='DELETE')delete db[id];return{};};return{root,manifest,db,writes,request};
- }
- test('查重部署默认只读,定向五函数更新、结构不变且二次不写',async t=>{const f=fixture(t);assert.equal((await deployDedup(f)).readOnly,true);assert.equal(f.writes.length,0);assert.equal(fs.existsSync(path.join(f.root,'evidence')),false);const result=await deployDedup({...f,apply:true});assert.equal(result.functionCodeWrites,5);assert.equal(result.businessWrites,0);assert.equal(result.schemaAndPermissionsPreserved,true);assert.equal(f.db.JDNsVUCXSo.name,'ksfCheckDuplicates');f.writes.length=0;assert.ok((await deployDedup({...f,apply:true})).functions.every(x=>x.action==='unchanged'));assert.equal(f.writes.length,0);});
- test('未知版本/撞名在写前停;中途失败恢复已更新代码',async t=>{const f=fixture(t),before=structuredClone(f.db);f.db.czT35BZZlx.code='external';await assert.rejects(()=>deployDedup({...f,apply:true}),{code:'UNKNOWN_FUNCTION_VERSION'});assert.equal(f.writes.length,0);f.db.czT35BZZlx=structuredClone(before.czT35BZZlx);let once=false;const request=async(m,r,b)=>{if(m==='PUT'&&r.endsWith('/gPjyoZbHhU')&&!once){once=true;throw Object.assign(Error('uncertain'),{code:'REMOTE_UNCERTAIN'});}return f.request(m,r,b);};await assert.rejects(()=>deployDedup({...f,request,apply:true}),e=>e.receipt.rollbackComplete===true);assert.deepEqual(f.db,before);});
- test('并发更新不覆盖他人改动,其他文件按备份回退',async t=>{const f=fixture(t),before=structuredClone(f.db);const request=async(m,r,b)=>{if(m==='PUT'&&r.endsWith('/gPjyoZbHhU')){await f.request(m,r,b);f.db.czT35BZZlx.code='concurrent';throw Object.assign(Error('lost'),{code:'REMOTE_UNCERTAIN'});}return f.request(m,r,b);};await assert.rejects(()=>deployDedup({...f,request,apply:true}),e=>e.receipt.rollbackComplete===false);assert.equal(f.db.czT35BZZlx.code,'concurrent');assert.equal(f.db.gPjyoZbHhU.code,before.gPjyoZbHhU.code);for(const id of ['ra9OWsmbtC','dWSrHp4FfQ','JDNsVUCXSo'])assert.equal(f.db[id].code,before[id].code);});
|