const { execSync, spawn } = require('child_process'); const { When, Then, Given } = require('@cucumber/cucumber'); const fs = require('fs'); const path = require('path'); const os = require('os'); const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..'); const MOCK_SERVER = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-server.py'); const DISPATCH_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'dispatch-workflow.sh'); function bash(cmd) { try { const out = execSync(`bash -c ${JSON.stringify(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 setupMock(seqJson) { execSync('lsof -ti :18080 2>/dev/null | xargs -r kill -9 2>/dev/null || true', { stdio: 'ignore' }); execSync('sleep 0.4', { stdio: 'ignore' }); const seqFile = path.join(os.tmpdir(), `cucumber_seq_${Date.now()}.json`); fs.writeFileSync(seqFile, seqJson); const idxFile = `${seqFile}.idx`; fs.writeFileSync(idxFile, '0'); const reqFile = path.join(os.tmpdir(), `cucumber_req_${Date.now()}.log`); const configFile = path.join(os.tmpdir(), `cucumber_cfg_${Date.now()}.txt`); fs.writeFileSync(configFile, `SEQUENCE\n${seqJson}\n${idxFile}`); const proc = spawn('python3', [MOCK_SERVER, '18080', configFile, reqFile], { cwd: PROJECT_ROOT, detached: true, stdio: 'ignore', }); proc.unref(); this._mockProc = proc; for (let i = 0; i < 20; i++) { try { execSync('curl -s --max-time 1 http://localhost:18080/', { stdio: 'ignore' }); fs.writeFileSync(idxFile, '0'); return; } catch (_) {} execSync('sleep 0.1', { stdio: 'ignore' }); } throw new Error('Mock failed to start'); } function runDispatch(args) { return bash(`export DISPATCH_ID="test123"; export DISPATCH_POLL_INTERVAL="0.1"; bash "${DISPATCH_SCRIPT}" ${args}`); } Given('a deployment has completed in the target environment', function () { }); Given('the test project repository exists with test definitions', function () { }); When('a test workflow is dispatched to a test project', function () { setupMock(JSON.stringify([ { code: 201 }, { code: 200, body: { workflow_runs: [{ id: 1, display_title: 'Workflow (test123)', run_number: 42, status: 'running' }] } }, { code: 200, body: { id: 1, status: 'completed', conclusion: 'success' } }, ])); const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"'); this.dispatchResult = r.status; }); Then('the pipeline waits until the test workflow finishes', function () { if (this.dispatchResult !== 0) throw new Error(`Expected 0, got ${this.dispatchResult}`); }); Then('the pipeline continues only after receiving a success result', function () { if (this.dispatchResult !== 0) throw new Error('Expected pipeline to continue after success'); }); When('a test workflow is dispatched and the tests fail', function () { setupMock(JSON.stringify([ { code: 201 }, { code: 200, body: { workflow_runs: [{ id: 1, display_title: 'Workflow (test123)', run_number: 42, status: 'running' }] } }, { code: 200, body: { id: 1, status: 'completed', conclusion: 'failure' } }, ])); const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"'); this.dispatchResult = r.status; }); Then('the calling pipeline reports failure', function () { if (this.dispatchResult !== 1) throw new Error(`Expected failure exit 1, got ${this.dispatchResult}`); }); When('a test workflow is dispatched but does not finish within the allowed time', function () { setupMock(JSON.stringify([ { code: 201 }, { code: 200, body: { workflow_runs: [] } }, { code: 200, body: { workflow_runs: [] } }, { code: 200, body: { workflow_runs: [] } }, { code: 200, body: { workflow_runs: [] } }, { code: 200, body: { workflow_runs: [] } }, ])); const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123" "0.05"'); this.dispatchResult = r.status; this.dispatchStderr = r.stderr; }); Then('the calling pipeline reports a timeout error', function () { if (this.dispatchResult !== 124) { throw new Error(`Expected timeout exit 124, got ${this.dispatchResult}. stderr: ${(this.dispatchStderr || '').substring(0,300)}`); } });