mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-27 04:49:49 -07:00
ci: reuse quality gate across CI and release workflows (#167)
This commit is contained in:
+8
-18
@@ -12,19 +12,6 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
|
||||
scripts: Record<string, string>;
|
||||
};
|
||||
|
||||
test('ci workflow lints changelog fragments', () => {
|
||||
assert.match(ciWorkflow, /bun run changelog:lint/);
|
||||
});
|
||||
|
||||
test('ci workflow checks pull requests for required changelog fragments', () => {
|
||||
assert.match(ciWorkflow, /bun run changelog:pr-check/);
|
||||
assert.match(ciWorkflow, /skip-changelog/);
|
||||
});
|
||||
|
||||
test('ci workflow verifies generated config examples stay in sync', () => {
|
||||
assert.match(ciWorkflow, /bun run verify:config-example/);
|
||||
});
|
||||
|
||||
test('package scripts expose a sharded maintained source coverage lane with lcov output', () => {
|
||||
assert.equal(
|
||||
packageJson.scripts['test:coverage:src'],
|
||||
@@ -32,11 +19,14 @@ test('package scripts expose a sharded maintained source coverage lane with lcov
|
||||
);
|
||||
});
|
||||
|
||||
test('ci workflow runs the maintained source coverage lane and uploads lcov output', () => {
|
||||
assert.match(ciWorkflow, /name: Coverage suite \(maintained source lane\)/);
|
||||
assert.match(ciWorkflow, /run: bun run test:coverage:src/);
|
||||
assert.match(ciWorkflow, /name: Upload coverage artifact/);
|
||||
assert.match(ciWorkflow, /path: coverage\/test-src\/lcov\.info/);
|
||||
test('ci delegates its gate instead of duplicating quality steps', () => {
|
||||
assert.match(
|
||||
ciWorkflow,
|
||||
/build-test-audit:\s*\n\s*uses: \.\/\.github\/workflows\/quality-gate\.yml/,
|
||||
);
|
||||
assert.doesNotMatch(ciWorkflow, /oven-sh\/setup-bun/);
|
||||
assert.doesNotMatch(ciWorkflow, /bun run test:coverage:src/);
|
||||
assert.doesNotMatch(ciWorkflow, /bun run test:env/);
|
||||
});
|
||||
|
||||
test('main docs deploy exists, serializes deploys, and uses Cloudflare credentials', () => {
|
||||
|
||||
@@ -33,11 +33,16 @@ test('prerelease workflow uses committed prerelease notes and never calls claude
|
||||
assert.doesNotMatch(prereleaseWorkflow, /run: bun run changelog:build/);
|
||||
});
|
||||
|
||||
test('prerelease workflow includes the environment suite in the gate sequence', () => {
|
||||
test('prerelease delegates its quality gate instead of duplicating quality steps', () => {
|
||||
assert.match(
|
||||
prereleaseWorkflow,
|
||||
/Test suite \(source\)\n\s*run: bun run test:fast\n\s*\n\s*- name: Environment suite(?: \(source\))?\n\s*run: bun run test:env\n\s*\n\s*- name: Coverage suite \(maintained source lane\)/,
|
||||
/quality-gate:\s*\n\s*permissions:\s*\n\s*contents: read\s*\n\s*uses: \.\/\.github\/workflows\/quality-gate\.yml/,
|
||||
);
|
||||
const qualityGateJob = prereleaseWorkflow.match(/quality-gate:[\s\S]*?(?=\n build-linux:)/)?.[0];
|
||||
assert.ok(qualityGateJob);
|
||||
assert.doesNotMatch(qualityGateJob, /oven-sh\/setup-bun/);
|
||||
assert.doesNotMatch(qualityGateJob, /bun run test:coverage:src/);
|
||||
assert.doesNotMatch(qualityGateJob, /bun run test:env/);
|
||||
});
|
||||
|
||||
test('prerelease workflow publishes GitHub prereleases and keeps them off latest', () => {
|
||||
@@ -46,15 +51,15 @@ test('prerelease workflow publishes GitHub prereleases and keeps them off latest
|
||||
assert.match(prereleaseWorkflow, /gh release create[\s\S]*--latest=false/);
|
||||
});
|
||||
|
||||
test('prerelease workflow scopes dependency caches by runner architecture', () => {
|
||||
test('prerelease packaging workflows scope dependency caches by runner architecture', () => {
|
||||
const archScopedCacheKeyMatches = prereleaseWorkflow.match(
|
||||
/key:\s*\${{\s*runner\.os\s*}}-\${{\s*runner\.arch\s*}}-bun-/g,
|
||||
);
|
||||
const archScopedRestoreKeyMatches = prereleaseWorkflow.match(
|
||||
/\${{\s*runner\.os\s*}}-\${{\s*runner\.arch\s*}}-bun-/g,
|
||||
);
|
||||
assert.equal(archScopedCacheKeyMatches?.length, 5);
|
||||
assert.ok((archScopedRestoreKeyMatches?.length ?? 0) >= 10);
|
||||
assert.equal(archScopedCacheKeyMatches?.length, 4);
|
||||
assert.ok((archScopedRestoreKeyMatches?.length ?? 0) >= 8);
|
||||
});
|
||||
|
||||
test('prerelease workflow builds and uploads all release platforms', () => {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const qualityGateWorkflowPath = resolve(__dirname, '../.github/workflows/quality-gate.yml');
|
||||
const qualityGateWorkflow = existsSync(qualityGateWorkflowPath)
|
||||
? readFileSync(qualityGateWorkflowPath, 'utf8').replace(/\r\n/g, '\n')
|
||||
: '';
|
||||
const verificationDocPath = resolve(__dirname, '../docs/workflow/verification.md');
|
||||
const verificationDoc = readFileSync(verificationDocPath, 'utf8').replace(/\r\n/g, '\n');
|
||||
|
||||
test('quality gate is a reusable workflow', () => {
|
||||
assert.match(qualityGateWorkflow, /on:\s*\n\s*workflow_call:/);
|
||||
assert.match(qualityGateWorkflow, /permissions:\s*\n\s*contents: read/);
|
||||
});
|
||||
|
||||
test('quality gate checkout does not persist GitHub credentials', () => {
|
||||
assert.match(
|
||||
qualityGateWorkflow,
|
||||
/uses: actions\/checkout@v4[\s\S]*?fetch-depth: 0[\s\S]*?submodules: true[\s\S]*?persist-credentials: false/,
|
||||
);
|
||||
});
|
||||
|
||||
test('quality gate installs Lua and runs the environment suite before coverage', () => {
|
||||
assert.match(qualityGateWorkflow, /name: Install Lua/);
|
||||
assert.match(qualityGateWorkflow, /apt-get install -y lua5\.4/);
|
||||
assert.match(
|
||||
qualityGateWorkflow,
|
||||
/Test suite \(source\)\n\s*run: bun run test:fast\n\s*\n\s*- name: Environment suite\n\s*run: bun run test:env\n\s*\n\s*- name: Coverage suite \(maintained source lane\)/,
|
||||
);
|
||||
});
|
||||
|
||||
test('quality gate uploads maintained source coverage', () => {
|
||||
assert.match(qualityGateWorkflow, /run: bun run test:coverage:src/);
|
||||
assert.match(qualityGateWorkflow, /name: Upload coverage artifact/);
|
||||
assert.match(qualityGateWorkflow, /path: coverage\/test-src\/lcov\.info/);
|
||||
});
|
||||
|
||||
test('quality gate keeps pull request changelog enforcement event-aware', () => {
|
||||
assert.match(qualityGateWorkflow, /bun run changelog:lint/);
|
||||
assert.match(qualityGateWorkflow, /if: github\.event_name == 'pull_request'/);
|
||||
assert.match(
|
||||
qualityGateWorkflow,
|
||||
/env:\s*\n\s*BASE_REF: \${{ github\.base_ref }}\s*\n\s*PR_LABELS: \${{ join\(github\.event\.pull_request\.labels\.\*\.name, ','\) }}\s*\n\s*run: bun run changelog:pr-check --base-ref "origin\/\$BASE_REF" --head-ref "HEAD" --labels "\$PR_LABELS"/,
|
||||
);
|
||||
assert.match(qualityGateWorkflow, /skip-changelog/);
|
||||
});
|
||||
|
||||
test('quality gate verifies generated config examples', () => {
|
||||
assert.match(qualityGateWorkflow, /bun run verify:config-example/);
|
||||
});
|
||||
|
||||
test('quality gate blocks on high-severity audit findings', () => {
|
||||
const securityAuditStep =
|
||||
qualityGateWorkflow.match(/- name: Security audit[\s\S]*?(?=\n {6}- name:|\n?$)/)?.[0] ?? '';
|
||||
const dependencyAuditPolicySection =
|
||||
verificationDoc.match(/## Dependency Audit Policy[\s\S]*?(?=\n## |\n?$)/)?.[0] ?? '';
|
||||
|
||||
assert.match(securityAuditStep, /run: bun audit --audit-level high/);
|
||||
assert.doesNotMatch(securityAuditStep, /continue-on-error: true/);
|
||||
assert.match(
|
||||
dependencyAuditPolicySection,
|
||||
/`bun audit --audit-level high` blocks the reusable quality gate/,
|
||||
);
|
||||
});
|
||||
@@ -81,11 +81,16 @@ test('release workflow verifies generated config examples before packaging artif
|
||||
assert.match(releaseWorkflow, /bun run verify:config-example/);
|
||||
});
|
||||
|
||||
test('release quality gate runs the maintained source coverage lane and uploads lcov output', () => {
|
||||
assert.match(releaseWorkflow, /name: Coverage suite \(maintained source lane\)/);
|
||||
assert.match(releaseWorkflow, /run: bun run test:coverage:src/);
|
||||
assert.match(releaseWorkflow, /name: Upload coverage artifact/);
|
||||
assert.match(releaseWorkflow, /path: coverage\/test-src\/lcov\.info/);
|
||||
test('release delegates its quality gate instead of duplicating quality steps', () => {
|
||||
assert.match(
|
||||
releaseWorkflow,
|
||||
/quality-gate:\s*\n\s*permissions:\s*\n\s*contents: read\s*\n\s*uses: \.\/\.github\/workflows\/quality-gate\.yml/,
|
||||
);
|
||||
const qualityGateJob = releaseWorkflow.match(/quality-gate:[\s\S]*?(?=\n build-linux:)/)?.[0];
|
||||
assert.ok(qualityGateJob);
|
||||
assert.doesNotMatch(qualityGateJob, /oven-sh\/setup-bun/);
|
||||
assert.doesNotMatch(qualityGateJob, /bun run test:coverage:src/);
|
||||
assert.doesNotMatch(qualityGateJob, /bun run test:env/);
|
||||
});
|
||||
|
||||
test('release build jobs install and cache stats dependencies before packaging', () => {
|
||||
|
||||
Reference in New Issue
Block a user