const { execSync } = require('child_process'); const { Before, 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 GITOPS_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'gitops-update.sh'); const MOCK_HELPERS = path.join(PROJECT_ROOT, 'tests', 'helpers'); const BASE_ENV = { INPUT_FILE: 'dev/Chart.yaml', YQ_TPL: '(.version) = "{{VERSION}}"', VERSION: '0.2.3', SOURCE_REPO: 'niko/app', SOURCE_COMMIT: 'abc123def456', GITOPS_REPO: 'niko/app-gitops', GITEA_API_URL: 'http://localhost:18080', GITEA_TOKEN: 'test-token', }; Before({ tags: '@mock' }, function () { process.env.PATH = `${MOCK_HELPERS}:${process.env.PATH}`; }); 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, stderr: '' }; } catch (e) { return { status: e.status, stdout: e.stdout || '', stderr: e.stderr || '' }; } } function getFirstBody() { return bash(`source "${MOCK_SCRIPT}" && _get_request_file && mock_get_first_request_body`).stdout.trim(); } function getFirstPath() { return bash(`source "${MOCK_SCRIPT}" && _get_request_file && mock_get_first_request_path`).stdout.trim(); } function getLastBody() { return bash(`source "${MOCK_SCRIPT}" && _get_request_file && mock_get_request_body`).stdout.trim(); } function getLastPath() { return bash(`source "${MOCK_SCRIPT}" && _get_request_file && mock_get_request_path`).stdout.trim(); } function requestCount() { const rf = bash(`source "${MOCK_SCRIPT}" && _get_request_file`).stdout.trim(); const count = bash(`grep -c '^POST ' "${rf}" 2>/dev/null || echo 0`).stdout.trim(); return parseInt(count, 10) || 0; } function runScript(envOverrides) { const env = { ...BASE_ENV, ...envOverrides }; const envStr = Object.entries(env) .map(([k, v]) => { const escaped = v.replace(/'/g, "'\\''"); return `${k}='${escaped}'`; }) .join(' '); return bash(`${envStr} bash "${GITOPS_SCRIPT}"`); } Given('insufficient environment variables are provided for the GitOps update', function () { this.missingVar = 'INPUT_FILE'; this.envOverrides = {}; this.envOverrides.INPUT_FILE = ''; }); Given('the GitOps repository clone will fail', function () { this.envOverrides = { GIT_MOCK_FAIL: '1' }; }); Given('a valid GitOps update dispatch', function () { this.envOverrides = {}; }); Given('the GitOps repo push will fail after the version is committed', function () { this.envOverrides = { GIT_MOCK_FAIL_PUSH: '1' }; }); When('the GitOps update script runs', function () { this.result = runScript(this.envOverrides || {}); }); Then('the caller commit shows a failure status with the missing variable name', function () { const body = getFirstBody(); if (!body.includes('"state":"failure"')) throw new Error(`Expected failure state, body: ${body.substring(0,200)}`); if (!body.includes('"context":"gitops/niko/app"')) throw new Error(`Expected gitops context, body: ${body.substring(0,200)}`); if (!body.includes(`"description":"${this.missingVar} is required`)) { throw new Error(`Expected description mentioning ${this.missingVar}, body: ${body.substring(0,200)}`); } const pathStr = getFirstPath(); if (!pathStr.includes('/repos/niko/app/statuses/')) throw new Error(`Expected source repo path, got: ${pathStr}`); }); Then('the caller commit shows a failure status', function () { const body = getFirstBody(); if (!body.includes('"state":"failure"')) throw new Error(`Expected failure state, body: ${body.substring(0,200)}`); if (!body.includes('"context":"gitops/niko/app"')) throw new Error(`Expected gitops context, body: ${body.substring(0,200)}`); const pathStr = getFirstPath(); if (!pathStr.includes('/repos/niko/app/statuses/')) throw new Error(`Expected source repo path, got: ${pathStr}`); }); Then('the script exits with error', function () { if (this.result.status === 0) throw new Error(`Expected non-zero exit, got 0. stderr: ${this.result.stderr}`); }); Then('the script exits successfully', function () { if (this.result.status !== 0) throw new Error(`Expected exit 0, got ${this.result.status}: ${this.result.stderr}`); }); Then('the caller commit shows a success status with a link to the GitOps commit', function () { const body = getFirstBody(); if (!body.includes('"state":"success"')) throw new Error(`Expected success state, body: ${body.substring(0,200)}`); if (!body.includes('"context":"gitops/niko/app"')) throw new Error(`Expected gitops context, body: ${body.substring(0,200)}`); if (!body.includes('niko/app-gitops/commits/')) throw new Error(`Expected link to GitOps commit, body: ${body.substring(0,200)}`); const pathStr = getFirstPath(); if (!pathStr.includes('/repos/niko/app/statuses/')) throw new Error(`Expected source repo path, got: ${pathStr}`); }); Then('the GitOps repo commit shows a success status with a link to the caller', function () { if (requestCount() < 2) throw new Error(`Expected at least 2 requests, got ${requestCount()}`); const body = getLastBody(); if (!body.includes('"state":"success"')) throw new Error(`Expected success state, body: ${body.substring(0,200)}`); if (!body.includes('"context":"source/niko/app"')) throw new Error(`Expected source context, body: ${body.substring(0,200)}`); if (!body.includes('niko/app/commits/abc123def456')) throw new Error(`Expected link to caller commit, body: ${body.substring(0,200)}`); const pathStr = getLastPath(); if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`); }); Then('the GitOps repo commit shows a failure status linking to the caller', function () { if (requestCount() < 2) throw new Error(`Expected at least 2 requests, got ${requestCount()}`); const body = getLastBody(); if (!body.includes('"state":"failure"')) throw new Error(`Expected failure state, body: ${body.substring(0,200)}`); if (!body.includes('"context":"source/niko/app"')) throw new Error(`Expected source context, body: ${body.substring(0,200)}`); if (!body.includes('niko/app/commits/abc123def456')) throw new Error(`Expected link to caller commit, body: ${body.substring(0,200)}`); const pathStr = getLastPath(); if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`); }); Then('the GitOps repo commit shows a source context status linking to the caller commit', function () { if (requestCount() < 2) throw new Error(`Expected at least 2 requests, got ${requestCount()}`); const body = getLastBody(); if (!body.includes('"state":"success"')) throw new Error(`Expected success state, body: ${body.substring(0,200)}`); if (!body.includes('"context":"source/niko/app"')) throw new Error(`Expected source context, body: ${body.substring(0,200)}`); if (!body.includes('niko/app/commits/abc123def456')) throw new Error(`Expected link to caller, body: ${body.substring(0,200)}`); const pathStr = getLastPath(); if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`); });