From ec6ec7ded671abd22219d470729f2fc24934428f Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Mon, 20 Jul 2026 15:35:10 +0800 Subject: [PATCH] test: strengthen deployment readiness contracts --- frontend/tests/health-deployment.test.ts | 58 +++++++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/frontend/tests/health-deployment.test.ts b/frontend/tests/health-deployment.test.ts index 887b0110..b2facec4 100644 --- a/frontend/tests/health-deployment.test.ts +++ b/frontend/tests/health-deployment.test.ts @@ -8,6 +8,38 @@ function serviceBlock(compose: string, service: string) { return match[1]; } +function webHealthcheckBlock(web: string) { + const match = web.match(/^ healthcheck:\n((?: .*\n?)*)/m); + assert.ok(match, "expected a web healthcheck in compose file"); + return match[1]; +} + +function workflowStepBlock(workflow: string, stepName: string) { + const match = workflow.match(new RegExp(`^ - name: ${stepName}\\n([\\s\\S]*?)(?=^ - name:|(?![\\s\\S]))`, "m")); + assert.ok(match, `expected ${stepName} workflow step`); + return match[1]; +} + +function escapeRegExp(value: string) { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +const testedShaExpression = "${{ github.event.workflow_run.head_sha || github.sha }}"; + +function assertWorkflowUsesTestedSha(workflow: string) { + const checkout = workflowStepBlock(workflow, "Checkout tested revision"); + const sync = workflowStepBlock(workflow, "Sync and rebuild"); + const verification = workflowStepBlock(workflow, "Verify production"); + const shaExpression = escapeRegExp(testedShaExpression); + + assert.match(checkout, new RegExp(`ref: ${shaExpression}`), "Checkout tested revision must check out the tested SHA"); + assert.match(sync, new RegExp(`DEPLOY_GIT_SHA: ${shaExpression}`), "Sync and rebuild must use the tested SHA"); + assert.match(sync, /GITHUB_SHA='\$DEPLOY_GIT_SHA'/, "Sync and rebuild must inject its SHA into the web runtime"); + assert.match(verification, new RegExp(`DEPLOY_GIT_SHA: ${shaExpression}`), "Verify production must use the tested SHA"); + assert.match(verification, /curl --fail --silent --show-error --retry 12 --retry-delay 5 https:\/\/jyotisha\.chat\/api\/health/); + assert.match(verification, /body\.deployment\?\.gitCommit !== process\.env\.DEPLOY_GIT_SHA/); +} + test("health endpoint exposes deployment identity for production verification", () => { const source = readFileSync(new URL("../src/app/api/health/route.ts", import.meta.url), "utf8"); @@ -22,20 +54,32 @@ test("production traffic waits for a healthy web container and retries short rep const caddyfile = readFileSync(new URL("../../deploy/Caddyfile", import.meta.url), "utf8"); const web = serviceBlock(compose, "web"); const caddy = serviceBlock(compose, "caddy"); + const healthcheck = webHealthcheckBlock(web); assert.match(web, /GITHUB_SHA: \$\{GITHUB_SHA\}/); assert.match(web, /healthcheck:\n\s+test: \["CMD", "node", "-e", "fetch\('http:\/\/127\.0\.0\.1:3000\/api\/health'\)\.then\(r=>\{if\(!r\.ok\)process\.exit\(1\)\}\)"\]/); - assert.match(web, /start_period: 30s/); - assert.match(web, /start_interval: 1s/); + assert.match(healthcheck, /^ interval: 30s$/m); + assert.match(healthcheck, /^ timeout: 5s$/m); + assert.match(healthcheck, /^ retries: 5$/m); + assert.match(healthcheck, /^ start_period: 30s$/m); + assert.match(healthcheck, /^ start_interval: 1s$/m); assert.match(caddy, /web:\n\s+condition: service_healthy/); assert.match(caddyfile, /reverse_proxy web:3000 \{\n\s+lb_try_duration 10s\n\s+lb_try_interval 250ms\n\s+\}/); }); -test("production verification accepts only the SHA exposed by the deployed health endpoint", () => { +test("production workflow consistently uses its tested revision from checkout through verification", () => { const workflow = readFileSync(new URL("../../.github/workflows/deploy-production.yml", import.meta.url), "utf8"); - assert.match(workflow, /DEPLOY_GIT_SHA: \$\{\{ github\.event\.workflow_run\.head_sha \|\| github\.sha \}\}/); - assert.match(workflow, /GITHUB_SHA='\$DEPLOY_GIT_SHA'/); - assert.match(workflow, /curl --fail --silent --show-error --retry 12 --retry-delay 5 https:\/\/jyotisha\.chat\/api\/health/); - assert.match(workflow, /body\.deployment\?\.gitCommit !== process\.env\.DEPLOY_GIT_SHA/); + assertWorkflowUsesTestedSha(workflow); +}); + +test("production workflow rejects a SHA mismatch in verification", () => { + const workflow = readFileSync(new URL("../../.github/workflows/deploy-production.yml", import.meta.url), "utf8"); + const verification = workflowStepBlock(workflow, "Verify production"); + const mismatchedVerification = verification.replace(testedShaExpression, "${{ github.sha }}"); + + assert.throws( + () => assertWorkflowUsesTestedSha(workflow.replace(verification, mismatchedVerification)), + /Verify production must use the tested SHA/, + ); });