| 1234567891011121314151617181920212223242526272829303132333435363738394041424344 |
- import fs from 'node:fs';
- import os from 'node:os';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import assert from 'node:assert/strict';
- import test from 'node:test';
- import {publishFiles} from '../scripts/publish-board-local.mjs';
- import {deployMemberRoster} from '../scripts/deploy-board-member-roster.mjs';
- const sha=b=>crypto.createHash('sha256').update(b).digest('hex');
- const root=path.resolve('.');
- function fixture(){
- const base=fs.mkdtempSync(path.join(os.tmpdir(),'ksf-board-deploy-test-')),targetDir=path.join(base,'site');fs.mkdirSync(targetDir);
- const manifest=JSON.parse(fs.readFileSync('deploy/board-local-20261111.json'));manifest.homepageSha256=sha('fixture homepage');
- fs.writeFileSync(path.join(targetDir,'index.html'),'fixture homepage');
- for(const f of manifest.files){const to=path.join(targetDir,f.path);fs.mkdirSync(path.dirname(to),{recursive:true});fs.copyFileSync(f.path,to);const bytes=fs.readFileSync(f.path);f.sha256=sha(bytes.includes(0)?bytes:Buffer.from(bytes.toString('utf8').replaceAll('\r\n','\n')));}
- const manifestPath=path.join(base,'manifest.json');fs.writeFileSync(manifestPath,JSON.stringify(manifest));
- return{base,manifest,targetDir,manifestPath,sourceDir:root};
- }
- test('完整前端发布预检只读,apply可重复且不替换首页',()=>{
- const f=fixture();try{
- const added=f.manifest.files.find(p=>p.allowMissing);fs.unlinkSync(path.join(f.targetDir,added.path));
- const plan=publishFiles(f);assert.equal(plan.readOnly,true);assert.deepEqual(plan.changes,[added.path]);assert.equal(fs.existsSync(path.join(f.targetDir,added.path)),false);
- const result=publishFiles({...f,apply:true});assert.equal(result.filesystemVerified,true);assert.equal(result.homepageModified,false);
- assert.deepEqual(publishFiles(f).changes,[]);assert.equal(fs.readFileSync(path.join(f.targetDir,'index.html'),'utf8'),'fixture homepage');
- }finally{fs.rmSync(f.base,{recursive:true,force:true});}
- });
- test('未知现场拒绝,写入失败恢复,新静态文件保持0644',()=>{
- const f=fixture();try{
- const added=f.manifest.files.filter(p=>p.allowMissing).slice(0,2);for(const p of added)fs.unlinkSync(path.join(f.targetDir,p.path));
- assert.throws(()=>publishFiles({...f,apply:true},{beforeWrite:p=>{if(p===added[1].path)throw Error('fixture');}}),e=>e.rollbackComplete===true);
- for(const p of added)assert.equal(fs.existsSync(path.join(f.targetDir,p.path)),false);
- const existing=f.manifest.files.find(p=>!p.allowMissing);fs.writeFileSync(path.join(f.targetDir,existing.path),'unknown concurrent content');
- assert.throws(()=>publishFiles({...f,apply:true}),{code:'UNKNOWN_TARGET_CONTENT'});
- fs.copyFileSync(existing.path,path.join(f.targetDir,existing.path));publishFiles({...f,apply:true});
- if(process.platform!=='win32')for(const p of added)assert.equal(fs.statSync(path.join(f.targetDir,p.path)).mode&0o777,0o644);
- }finally{fs.rmSync(f.base,{recursive:true,force:true});}
- });
- test('成员接口未知版本在写入前停止',async()=>{
- let writes=0;await assert.rejects(deployMemberRoster({manifest:{functions:[{objectId:'a',name:'n',path:'/n',beforeSha256:sha('old'),afterSha256:sha('new')}]},request:async(method)=>{if(method!=='GET')writes++;return{name:'n',path:'/n',code:'unexpected'};}}),/UNKNOWN_MEMBER_FUNCTION_BASELINE/);assert.equal(writes,0);
- });
- test('成员接口已发布版本只读和apply均不重复写入',async()=>{
- let writes=0;const cfg={manifest:{functions:[{objectId:'a',name:'n',path:'/n',beforeSha256:sha('old'),afterSha256:sha('new')}]},request:async(method)=>{if(method!=='GET')writes++;return{name:'n',path:'/n',code:'new'};}};
- assert.equal((await deployMemberRoster(cfg)).readOnly,true);assert.equal((await deployMemberRoster({...cfg,apply:true})).functions[0].action,'unchanged');assert.equal(writes,0);
- });
|