import assert from 'node:assert/strict'; import { test } from 'node:test'; import { assertRequestedVersionNotBelowChannel, buildOssutilArgs, issueGlobalVersion, maxVersion, nextVersion, previewNextGlobalVersion, resolveSeedBaseline, } from './agc-global-version.mjs'; function jsonResponse(payload, status = 200) { return { ok: status >= 200 && status < 300, status, json: async () => payload, }; } /** 以 URL 为键的假 OSS:只读 fetch + 记录写入。 */ function createFakeOss({ objects = {} } = {}) { const state = { ...objects }; const writes = []; return { state, writes, fetchImpl: async (url) => { const key = String(url).replace(/^https?:\/\/[^/]+\//u, ''); if (!(key in state)) return jsonResponse(null, 404); return jsonResponse(state[key]); }, writeImpl: (payload, options = {}) => { writes.push({ payload, dryRun: Boolean(options.dryRun) }); if (!options.dryRun) state['agc/global-version.json'] = payload; return { written: !options.dryRun, payload }; }, }; } test('播种基线取仓库版本、各渠道清单与旧迁移指针的最大值', async () => { const oss = createFakeOss({ objects: { 'agc/dev-win/latest.json': { version: '0.1.57' }, 'agc/dev-mac/latest.json': { version: '0.1.12' }, 'agc/latest.json': { version: '0.1.60' }, }, }); assert.equal( await resolveSeedBaseline({ repoVersion: '0.1.48', env: {}, fetchImpl: oss.fetchImpl, }), '0.1.60', ); assert.equal(maxVersion('0.1.9', '0.1.10', null), '0.1.10'); }); test('无总号时按播种基线发首号,并把总号写回唯一事实源', async () => { const oss = createFakeOss({ objects: { 'agc/dev-win/latest.json': { version: '0.1.57' }, 'agc/dev-mac/latest.json': { version: '0.1.12' }, }, }); const issued = await issueGlobalVersion({ channel: 'dev-win', commit: 'a'.repeat(40), buildId: '319', repoVersion: '0.1.48', env: {}, fetchImpl: oss.fetchImpl, writeImpl: oss.writeImpl, now: () => '2026-09-20T00:00:00.000Z', }); assert.equal(issued, '0.1.58'); assert.deepEqual( oss.writes.map((entry) => entry.payload.version), ['0.1.58'], ); assert.equal(oss.state['agc/global-version.json'].version, '0.1.58'); assert.equal(oss.state['agc/global-version.json'].channel, 'dev-win'); assert.equal(oss.state['agc/global-version.json'].buildId, '319'); }); test('已有总号时只递增,不再回看渠道清单', async () => { const oss = createFakeOss({ objects: { 'agc/global-version.json': { version: '0.2.7' }, // 渠道清单被手工改小也不能把总号拉回去。 'agc/dev-win/latest.json': { version: '0.1.10' }, }, }); const issued = await issueGlobalVersion({ channel: 'dev-mac', env: {}, fetchImpl: oss.fetchImpl, writeImpl: oss.writeImpl, }); assert.equal(issued, '0.2.8'); assert.equal(oss.state['agc/global-version.json'].version, '0.2.8'); }); test('dry-run 只预览下一位,不写回、不烧号', async () => { const oss = createFakeOss({ objects: { 'agc/global-version.json': { version: '0.3.4' } }, }); const preview = await previewNextGlobalVersion({ env: {}, fetchImpl: oss.fetchImpl, }); assert.equal(preview, '0.3.5'); assert.equal(oss.writes.length, 0); const issued = await issueGlobalVersion({ channel: 'dev-win', env: { AGC_RELEASE_DRY_RUN: '1' }, fetchImpl: oss.fetchImpl, writeImpl: oss.writeImpl, }); assert.equal(issued, '0.3.5'); assert.deepEqual( oss.writes.map((entry) => entry.dryRun), [true], ); assert.equal(oss.state['agc/global-version.json'].version, '0.3.4'); }); test('传入低于本渠道清单的号时失败关闭', () => { assert.equal( assertRequestedVersionNotBelowChannel({ requested: '0.1.60', channelVersion: '0.1.60', channel: 'dev-win', }), '0.1.60', ); assert.throws( () => assertRequestedVersionNotBelowChannel({ requested: '0.1.59', channelVersion: '0.1.60', channel: 'dev-win', }), /低于 dev-win 渠道当前清单版本/u, ); assert.equal( assertRequestedVersionNotBelowChannel({ requested: '0.1.1', channelVersion: null, channel: 'dev-mac', }), '0.1.1', ); }); test('写后回读不一致(并发发号)时失败关闭', async () => { const oss = createFakeOss({ objects: { 'agc/global-version.json': { version: '0.5.1' } }, }); await assert.rejects( issueGlobalVersion({ channel: 'dev-win', env: {}, // 模拟另一个发号进程在写入后覆盖了总号。 writeImpl: (payload, options) => { const result = oss.writeImpl(payload, options); // 另一个发号进程紧随其后覆盖总号。 oss.state['agc/global-version.json'] = { version: '0.5.9' }; return result; }, fetchImpl: oss.fetchImpl, }), /写后回读不一致/u, ); }); test('nextVersion 只在 patch 位递增', () => { assert.equal(nextVersion('0.1.9'), '0.1.10'); assert.equal(nextVersion('1.0.0'), '1.0.1'); assert.throws(() => nextVersion('0.1'), /不是有效的三段版本号/u); }); test('ossutil 参数默认使用 v1 签名,并可按需带 region 与 v4', () => { const base = { args: ['cp', '--force', '/tmp/a.json', 'oss://agc-dev/agc/global-version.json'], endpoint: 'oss-rg-china-mainland.aliyuncs.com', accessKeyId: 'id', accessKeySecret: 'secret', env: {}, }; const v1 = buildOssutilArgs(base); assert.equal(v1[v1.indexOf('--sign-version') + 1], 'v1'); assert.ok(!v1.includes('--region')); assert.equal(v1[v1.indexOf('--access-key-id') + 1], 'id'); assert.equal(v1[v1.indexOf('--access-key-secret') + 1], 'secret'); const v4 = buildOssutilArgs({ ...base, env: { AGC_OSS_SIGN_VERSION: 'v4', AGC_OSS_REGION: 'cn-beijing' }, }); assert.equal(v4[v4.indexOf('--region') + 1], 'cn-beijing'); assert.equal(v4[v4.indexOf('--sign-version') + 1], 'v4'); });