101 lines
5.4 KiB
JavaScript
101 lines
5.4 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { promotionPlan, promoteStableRelease } from './promote-stable-release.mjs';
|
|
|
|
const version='3.29.0.20261014.2';
|
|
const manifest={schemaVersion:1,channel:'versioned',productVersion:version,platform:'linux/amd64',releaseId:'release-id',managedUpdateAllowed:true,scheduledUpdateAllowed:true,rollbackSafe:false,publishedAt:'2026-10-14T00:00:00Z',
|
|
components:[
|
|
['server-main','comfy-ai/comfy-server',''],['web','comfy-ai/one-ai',''],
|
|
['web-no-ssr','comfy-ai/one-ai','-no-ssr'],['ws-gateway','easyaigc/wsgateway',''],
|
|
['model-conversion-worker','easyaigc/model-conversion-worker',''],
|
|
].map(([key,image,suffix],index)=>({key,image:'registry.cn-shanghai.aliyuncs.com/'+image,tag:version+suffix,digest:'sha256:'+String(index+1).repeat(64),buildId:'source-sha'}))};
|
|
|
|
function fixture() {
|
|
const plan=promotionPlan(manifest,version,version);
|
|
const tags=new Map(plan.flatMap(image=>[[image.source,image.digest],[image.stable,'sha256:'+'9'.repeat(64)]]));
|
|
const calls=[];
|
|
const run=args=>{
|
|
calls.push(args);
|
|
if (args[0]==='manifest') return JSON.stringify({Descriptor:{platform:{os:'linux',architecture:'amd64'}}});
|
|
if (args[2]==='inspect') {
|
|
const ref=args[3];
|
|
// 真实单平台 imagetools 输出没有 platform,需读取 verbose manifest。
|
|
return JSON.stringify({digest:tags.get(ref),...(ref.includes('comfy-server') ? {} : {manifests:[{platform:{os:'linux',architecture:'amd64'}}]})});
|
|
}
|
|
assert.equal(args[2],'create');
|
|
assert.ok(args.includes('--prefer-index=false'));
|
|
const ref=args[args.indexOf('--tag')+1];
|
|
const digest=args.at(-1).split('@')[1];
|
|
tags.set(ref,digest);
|
|
return '';
|
|
};
|
|
return {plan,tags,calls,run};
|
|
}
|
|
|
|
test('未明确确认同一个版本及浮动版本都不调用Docker',()=>{
|
|
let called=false;
|
|
for (const [candidate,confirm] of [[version,undefined],[version,'3.29.0.20261014.3'],['latest','latest']]) {
|
|
assert.throws(()=>promoteStableRelease({manifest,version:candidate,confirmedVersion:confirm,run:()=>{called=true;}}),/人工指定/);
|
|
}
|
|
assert.equal(called,false);
|
|
});
|
|
|
|
test('候选必须包括SSR/noSSR,并拒绝重复、错误镜像和浮动源标签',()=>{
|
|
const variant = {...manifest.components[1],key:'hmg-ai',tag:version+'-hmg-ai'};
|
|
assert.equal(promotionPlan({...manifest,components:[...manifest.components,variant]},version,version).at(-1).stable,variant.image+':latest-hmg-ai');
|
|
for (const components of [manifest.components.filter(item=>item.key!=='web-no-ssr'),
|
|
[...manifest.components,manifest.components[0]],
|
|
manifest.components.map((item,index)=>index?item:{...item,image:'wrong/image'}),
|
|
manifest.components.map((item,index)=>index?item:{...item,tag:'latest'})]) {
|
|
assert.throws(()=>promotionPlan({...manifest,components},version,version));
|
|
}
|
|
});
|
|
|
|
test('全部固定产物先核验;任一digest不匹配时稳定标签和清单保持',()=>{
|
|
const f=fixture();
|
|
f.tags.set(f.plan.at(-1).source,'sha256:'+'8'.repeat(64));
|
|
assert.throws(()=>promoteStableRelease({manifest,version,confirmedVersion:version,outputPath:'unused',run:f.run}),/核验失败/);
|
|
assert.ok(f.calls.every(args=>args[2]!=='create'));
|
|
assert.ok(f.plan.every(image=>f.tags.get(image.stable)==='sha256:'+'9'.repeat(64)));
|
|
});
|
|
|
|
test('dry-run核验固定版本且不写任何稳定标签或文件',()=>{
|
|
const f=fixture();
|
|
assert.equal(promoteStableRelease({manifest,version,confirmedVersion:version,dryRun:true,run:f.run}).status,'DRY_RUN');
|
|
assert.ok(f.calls.every(args=>args[2]!=='create'));
|
|
});
|
|
|
|
test('人工提升只按已核验digest补稳定标签,两种前端分别保留;不构建业务或改源版本',()=>{
|
|
const dir=mkdtempSync(join(tmpdir(),'easyai-promote-'));
|
|
try {
|
|
const path=join(dir,'stable.json');
|
|
const f=fixture();
|
|
const result=promoteStableRelease({manifest,version,confirmedVersion:version,outputPath:path,run:f.run});
|
|
assert.equal(result.status,'PROMOTED');
|
|
assert.ok(f.plan.every(image=>f.tags.get(image.stable)===image.digest));
|
|
assert.ok(f.plan.every(image=>f.tags.get(image.source)===image.digest));
|
|
assert.equal(JSON.parse(readFileSync(path)).channel,'stable');
|
|
assert.deepEqual(JSON.parse(readFileSync(path)).components.map(item=>item.key),['server-main','web','ws-gateway']);
|
|
assert.ok(f.calls.every(args=>args[0]!=='build' && args[0]!=='push'));
|
|
} finally { rmSync(dir,{recursive:true,force:true}); }
|
|
});
|
|
|
|
test('稳定别名写入后回执失败时恢复原指针,稳定清单不提前写入',()=>{
|
|
const dir=mkdtempSync(join(tmpdir(),'easyai-promote-fail-'));
|
|
try {
|
|
const path=join(dir,'stable.json'); writeFileSync(path,'old stable manifest');
|
|
const f=fixture(); let once=true;
|
|
const run=args=>{
|
|
const output=f.run(args);
|
|
if (args[2]==='create' && args.includes(f.plan[2].stable) && once) { once=false; throw new Error('write succeeded but response lost'); }
|
|
return output;
|
|
};
|
|
assert.throws(()=>promoteStableRelease({manifest,version,confirmedVersion:version,outputPath:path,run}),/未完成.*已恢复/);
|
|
assert.ok(f.plan.every(image=>f.tags.get(image.stable)==='sha256:'+'9'.repeat(64)));
|
|
assert.equal(readFileSync(path,'utf8'),'old stable manifest');
|
|
} finally { rmSync(dir,{recursive:true,force:true}); }
|
|
});
|