const { execSync } = require('child_process'); const { When, Then } = require('@cucumber/cucumber'); const path = require('path'); const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..'); const MOCK_SCRIPT = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-api.sh'); const REPORT_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'report-status.sh'); function bash(cmd) { try { const out = execSync(`bash -c '${cmd}'`, { cwd: PROJECT_ROOT, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], }); return { status: 0, stdout: out }; } catch (e) { return { status: e.status, stdout: e.stdout || '', stderr: e.stderr || '' }; } } function bashQuiet(cmd) { execSync(`bash -c '${cmd}'`, { cwd: PROJECT_ROOT, stdio: 'ignore', }); } function runReportStatus(args) { return bash(`export GITEA_API_URL="http://localhost:18080" GITEA_TOKEN="test-token-abc123" GIT_PAGES_URL="https://reports.example.com" GITHUB_REPOSITORY="test-owner/test-repo" GITHUB_SHA="abc123def456789012345678901234567890abcd" GITHUB_RUN_ID="42"; bash "${REPORT_SCRIPT}" ${args}`); } function getMockBody() { return bash(`source "${MOCK_SCRIPT}" && _get_request_file && mock_get_request_body`).stdout.trim(); } function getMockPath() { return bash(`source "${MOCK_SCRIPT}" && _get_request_file && mock_get_request_path`).stdout.trim(); } When('a build step starts executing', function () { const r = runReportStatus('pending "Building project"'); if (r.status !== 0) throw new Error(`Expected exit 0, got ${r.status}: ${r.stderr}`); }); Then('the commit shows a pending status with a description of the step', function () { const body = getMockBody(); if (!body.includes('"state":"pending"')) throw new Error('Expected pending status'); if (!body.includes('"description":"Building project"')) throw new Error('Expected description'); }); When('a build step completes successfully and reports its results', function () { const r = runReportStatus('success "Unit tests OK" unit-test cucumber/'); if (r.status !== 0) throw new Error(`Expected exit 0, got ${r.status}`); }); Then('the commit shows a success status with a clickable link to the results', function () { const body = getMockBody(); if (!body.includes('"state":"success"')) throw new Error('Expected success status'); if (!body.includes('"target_url":"https://reports.example.com/test-owner/test-repo/reports/abc123de/cucumber/"')) throw new Error('Expected URL'); }); When('a build step fails', function () { const r = runReportStatus('failure "Tests failed: 3 of 10"'); if (r.status !== 0) throw new Error(`Expected exit 0, got ${r.status}`); }); Then('the commit shows a failure status with a description of what went wrong', function () { const body = getMockBody(); if (!body.includes('"state":"failure"')) throw new Error('Expected failure status'); if (!body.includes('"description":"Tests failed: 3 of 10"')) throw new Error('Expected failure description'); }); When('several build steps each report their own status to the same commit', function () { runReportStatus('pending "Build started" ci-build'); execSync('sleep 0.3', { stdio: 'ignore' }); runReportStatus('success "Unit tests passed" unit-test'); execSync('sleep 0.3', { stdio: 'ignore' }); runReportStatus('success "Integration tests passed" integration-test'); }); Then('each status appears under a unique label on the commit', function () { const requestFile = bash(`source "${MOCK_SCRIPT}" && _get_request_file`).stdout.trim(); if (!requestFile || requestFile === '/dev/null') throw new Error('Mock request file not found'); bashQuiet('sync'); const allRequestsRaw = bash(`cat "${requestFile}"`); const allRequests = allRequestsRaw.stdout; const postCount = (allRequests.match(/POST /g) || []).length; if (postCount < 3) throw new Error(`Expected 3 POST requests, got ${postCount}. Content: ${allRequests.substring(0, 1000)}`); if (!allRequests.includes('"context":"ci-build"')) throw new Error('Missing ci-build'); if (!allRequests.includes('"context":"unit-test"')) throw new Error('Missing unit-test'); if (!allRequests.includes('"context":"integration-test"')) throw new Error('Missing integration-test'); }); When('a deployment finishes for a commit that originated from another repository', function () { const r = runReportStatus('success "Deployed to staging" deploy-staging'); if (r.status !== 0) throw new Error(`Expected exit 0, got ${r.status}`); }); Then('the source commit shows the deployment status alongside the build status', function () { const body = getMockBody(); if (!body.includes('"state":"success"')) throw new Error('Expected success'); if (!body.includes('"context":"deploy-staging"')) throw new Error('Expected deploy-staging context'); const pathStr = getMockPath(); if (!pathStr.includes('test-owner/test-repo')) throw new Error('Expected default repo target'); if (!pathStr.includes('abc123def456789012345678901234567890abcd')) throw new Error('Expected default commit'); }); When('a build step tries to report status but the build system is unavailable', function () { bashQuiet(`source "${MOCK_SCRIPT}" && mock_stop`); execSync('sleep 0.3', { stdio: 'ignore' }); bashQuiet(`source "${MOCK_SCRIPT}" && mock_set_response 500 && mock_start`); const r = runReportStatus('success "Should fail"'); this.reportStatusFailed = (r.status !== 0); }); Then('the pipeline fails with a clear error message', function () { if (!this.reportStatusFailed) throw new Error('Expected pipeline to fail (exit code != 0)'); });