qwen-code/scripts/tests/desktop-oss-workflow.test.js
易良 464e8910e8
fix(desktop): harden release pipeline (#9009)
* fix(desktop): harden release pipeline

* fix(desktop): resolve release hardening review
2026-08-12 16:38:12 +00:00

157 lines
6.1 KiB
JavaScript

/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
import { getWorkflowJob, getWorkflowStep } from './workflow-helpers.js';
const releaseWorkflow = readFileSync(
'.github/workflows/desktop-release.yml',
'utf8',
);
const syncWorkflow = readFileSync(
'.github/workflows/sync-desktop-to-oss.yml',
'utf8',
);
const tauriConfig = JSON.parse(
readFileSync('packages/desktop-shell/src-tauri/tauri.conf.json', 'utf8'),
);
describe('Desktop OSS mirror workflow', () => {
it('mirrors only published stable Desktop releases', () => {
expect(syncWorkflow).not.toContain('pull_request:');
expect(releaseWorkflow).toContain(
"desktop-release-${{ inputs.dry_run && inputs.version || 'publish' }}",
);
const prepare = getWorkflowStep(
getWorkflowJob(releaseWorkflow, 'prepare'),
'Resolve version',
);
expect(prepare).toContain("IS_DRAFT: '${{ inputs.draft }}'");
expect(prepare).toContain("IS_DRY_RUN: '${{ inputs.dry_run }}'");
expect(prepare).toContain("IS_PRERELEASE: '${{ inputs.prerelease }}'");
expect(prepare).toContain(
'Published stable Desktop versions must use X.Y.Z',
);
expect(prepare).toContain(
'Desktop prereleases must use a SemVer prerelease suffix',
);
const syncOss = getWorkflowJob(releaseWorkflow, 'sync-oss');
expect(syncOss).toContain(
"if: \"${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == false && inputs.draft == false && inputs.prerelease == false && github.repository == 'QwenLM/qwen-code' }}\"",
);
expect(syncOss).toContain("- 'publish'");
expect(syncOss).toContain("source: 'artifact'");
expect(syncOss).not.toContain('secrets: inherit');
});
it('passes only the OSS credentials into the reusable workflow', () => {
expect(releaseWorkflow).toContain("permissions:\n contents: 'read'");
const syncOss = getWorkflowJob(releaseWorkflow, 'sync-oss');
expect(syncOss).toContain(
"permissions:\n actions: 'read'\n contents: 'read'",
);
for (const secret of [
'ALIYUN_OSS_ACCESS_KEY_ID',
'ALIYUN_OSS_ACCESS_KEY_SECRET',
]) {
expect(syncWorkflow).toContain(`${secret}:\n required: true`);
expect(syncOss).toContain(`${secret}: '\${{ secrets.${secret} }}'`);
}
});
it('publishes verified versioned assets before advancing the OSS feed', () => {
const sync = getWorkflowJob(syncWorkflow, 'sync');
const prepare = getWorkflowStep(sync, 'Verify and prepare mirror assets');
expect(prepare).toContain(
'--base-url "${ALIYUN_OSS_PUBLIC_BASE_URL}/desktop/v${VERSION}"',
);
expect(prepare).toContain('sha256sum -- * > SHA256SUMS.txt');
const upload = getWorkflowStep(
sync,
'Upload versioned assets to Aliyun OSS',
);
expect(upload).toContain('--prefix "desktop/v${VERSION}"');
const latest = getWorkflowStep(
sync,
'Publish latest manifest to Aliyun OSS',
);
expect(latest).toContain("--prefix 'desktop/latest'");
expect(latest).toContain('dist/desktop/desktop-latest.json');
expect(latest).not.toContain('.dmg');
const verifyIndex = sync.indexOf(
"name: 'Verify versioned assets on Aliyun OSS'",
);
expect(verifyIndex).toBeGreaterThan(0);
expect(verifyIndex).toBeLessThan(
sync.indexOf("name: 'Publish latest manifest to Aliyun OSS'"),
);
expect(
getWorkflowStep(sync, 'Verify versioned assets on Aliyun OSS'),
).toContain('sha256sum -c SHA256SUMS.txt');
expect(
getWorkflowStep(sync, 'Verify latest manifest on Aliyun OSS'),
).toContain('cmp ');
});
it('advances the OSS feed only for the current GitHub stable version', () => {
const publish = getWorkflowJob(releaseWorkflow, 'publish');
const updateFeed = getWorkflowStep(publish, 'Update stable updater feed');
expect(updateFeed).toContain('sort -V');
expect(updateFeed).toContain(
'Desktop $RELEASE_VERSION will not replace newer stable feed $current',
);
const sync = getWorkflowJob(syncWorkflow, 'sync');
const check = getWorkflowStep(
sync,
'Check whether release matches GitHub stable feed',
);
expect(check).toContain("gh release download 'desktop-latest'");
expect(check).toContain("SOURCE: '${{ steps.release.outputs.source }}'");
expect(check).toContain('elif [ "$SOURCE" = \'artifact\' ]; then');
expect(check).toContain('sort -V');
expect(check).toContain('GitHub stable feed is already newer at $actual');
expect(check).toContain(
'GitHub stable feed is $actual after publishing Desktop $expected',
);
expect(check).toContain('echo \'matches=true\' >> "$GITHUB_OUTPUT"');
expect(check).toContain('echo \'matches=false\' >> "$GITHUB_OUTPUT"');
expect(check).toContain(
'expected="$(jq -r \'.version\' dist/desktop/desktop-latest.json)"',
);
expect(check).toContain(
'actual="$(jq -r \'.version\' "$directory/desktop-latest.json")"',
);
expect(
getWorkflowStep(sync, 'Publish latest manifest to Aliyun OSS'),
).toContain('if: "${{ steps.latest.outputs.matches == \'true\' }}"');
expect(
getWorkflowStep(sync, 'Verify latest manifest on Aliyun OSS'),
).toContain('if: "${{ steps.latest.outputs.matches == \'true\' }}"');
});
it('sync workflow validates stable-only releases in the reusable job', () => {
expect(syncWorkflow).not.toContain('pull_request:');
const sync = getWorkflowJob(syncWorkflow, 'sync');
const resolve = getWorkflowStep(sync, 'Resolve release');
expect(resolve).toContain('^[0-9]+\\.[0-9]+\\.[0-9]+$');
expect(resolve).toContain("!= 'artifact'");
expect(resolve).toContain("!= 'release'");
expect(getWorkflowStep(sync, 'Download GitHub release assets')).toContain(
'.isDraft == false and .isPrerelease == false',
);
});
it('keeps the workflow default aligned with the shipped updater endpoint', () => {
const firstEndpoint = tauriConfig.plugins.updater.endpoints[0];
expect(syncWorkflow).toContain(new URL(firstEndpoint).origin);
});
});