| 123456789101112131415161718192021222324252627282930 |
- import test from 'node:test';
- import assert from 'node:assert/strict';
- import fs from 'node:fs';
- import path from 'node:path';
- import os from 'node:os';
- import crypto from 'node:crypto';
- import {deployIdentity} from '../scripts/deploy-identity.mjs';
- const sha=x=>crypto.createHash('sha256').update(x).digest('hex');
- function fixture(t){
- const root=fs.mkdtempSync(path.join(os.tmpdir(),'ksf-identity-deploy-'));t.after(()=>{assert.equal(path.dirname(root),fs.realpathSync(os.tmpdir()));assert.ok(path.basename(root).startsWith('ksf-identity-deploy-'));fs.rmSync(root,{recursive:true});});
- const functions=[{name:'ksfStartWorkshopSession',objectId:'yKpUay6RDG'},{name:'ksfGetIngestContract',objectId:'dWSrHp4FfQ'}],db={},puts=[];fs.mkdirSync(path.join(root,'dist'));
- const manifest={version:'ksf.identity-deployment.v1',appId:'FKieRbIoGt_main',functions:functions.map((x,i)=>{const prior='function previous'+i+'(){}',code='function next'+i+'(){}';db[x.objectId]={...x,path:'/'+x.name,code:prior};const file='dist/'+x.name+'.js';fs.writeFileSync(path.join(root,file),code);return{...x,path:'/'+x.name,file,beforeSha256:sha(prior),sha256:sha(code)};})};
- fs.writeFileSync(path.join(root,'dist/identity-manifest.json'),JSON.stringify(manifest));
- const request=async(method,route,body)=>{if(route==='/schemas')return{results:[{className:'KfSession',fields:{meta:{type:'Object'}},classLevelPermissions:{find:{}}}]};const id=route.split('/').at(-1);if(method==='PUT'){puts.push(id);db[id].code=body.code;}return structuredClone(db[id]);};return{root,db,puts,request,manifest};
- }
- test('身份发布默认只读;仅更新固定两函数,复验结构权限且重放不写',async t=>{
- const f=fixture(t),plan=await deployIdentity(f);assert.equal(plan.readOnly,true);assert.equal(f.puts.length,0);assert.equal(fs.existsSync(path.join(f.root,'evidence')),false);
- const result=await deployIdentity({...f,apply:true});assert.equal(result.ok,true);assert.deepEqual(f.puts,['yKpUay6RDG','dWSrHp4FfQ']);assert.equal(result.schemaAndPermissionsPreserved,true);assert.ok(fs.existsSync(result.backup));
- f.puts.length=0;assert.ok((await deployIdentity({...f,apply:true})).functions.every(x=>x.action==='unchanged'));assert.equal(f.puts.length,0);
- });
- test('未知线上版本、损坏包在写前停止;中途失败恢复已更新函数',async t=>{
- const f=fixture(t),original=structuredClone(f.db);f.db.yKpUay6RDG.code='function externalChange(){}';await assert.rejects(()=>deployIdentity({...f,apply:true}),{code:'UNKNOWN_FUNCTION_VERSION'});assert.equal(f.puts.length,0);f.db.yKpUay6RDG=structuredClone(original.yKpUay6RDG);
- const file=path.join(f.root,f.manifest.functions[0].file),bytes=fs.readFileSync(file);fs.appendFileSync(file,'// broken');await assert.rejects(()=>deployIdentity({...f,apply:true}),{code:'PACKAGE_HASH_MISMATCH'});assert.equal(f.puts.length,0);fs.writeFileSync(file,bytes);
- let failed=false;const request=async(m,r,b)=>{if(m==='PUT'&&r.endsWith('/dWSrHp4FfQ')&&!failed){failed=true;throw Object.assign(Error('fixture'),{code:'REMOTE_UNCERTAIN'});}return f.request(m,r,b);};
- await assert.rejects(()=>deployIdentity({...f,request,apply:true}),e=>{assert.equal(e.receipt.rollbackComplete,true);return true;});assert.deepEqual(f.db,original);
- });
- test('失败回滚不覆盖其他维护者并发改动',async t=>{
- const f=fixture(t);let failed=false;const request=async(m,r,b)=>{if(m==='PUT'&&r.endsWith('/dWSrHp4FfQ')&&!failed){failed=true;f.db.yKpUay6RDG.code='function concurrentOwnerChange(){}';throw Object.assign(Error('fixture'),{code:'REMOTE_UNCERTAIN'});}return f.request(m,r,b);};
- await assert.rejects(()=>deployIdentity({...f,request,apply:true}),e=>{assert.equal(e.receipt.rollbackComplete,false);return true;});assert.equal(f.db.yKpUay6RDG.code,'function concurrentOwnerChange(){}');
- });
|