cucumber testejä lisää
CI Feature / Load example-gitea-env.conf to pipeline env (push) Successful in 34s
acc-tests Cucumber test report
CI Feature / Cucumber tests (push) Failing after 45s
unit-tests Bats test report
CI Feature / Bats tests (push) Successful in 1m36s
CI Feature / Report Summary (push) Successful in 5s

This commit is contained in:
moilanik
2026-06-21 16:10:41 +03:00
parent 86e73d87d3
commit 5c9df73a66
4 changed files with 232 additions and 68 deletions
+39 -10
View File
@@ -1,15 +1,44 @@
Feature: GitOps version update
As a developer
I want to automatically update version references in a GitOps repo
So that deployment is triggered with the correct artifact version
Feature: GitOps update
As a GitOps repository
I want to update version references and report results back to the caller
So that the deployment chain is traceable from source to GitOps commit
Background:
Given a project repository exists in Gitea
And a commit has been pushed to the repository
@mock @real
Scenario: GitOps repo receives version bump dispatch
When a build completes successfully and dispatches a GitOps update
Then the GitOps repo has a new commit with the updated version
And the code repo shows a gitops status link to the GitOps commit
And the GitOps repo shows a source status link to the code commit
@mock
Scenario: Not enough env vars — caller commit gets failure status
Given insufficient environment variables are provided for the GitOps update
When the GitOps update script runs
Then the caller commit shows a failure status with the missing variable name
And the script exits with error
@mock
Scenario: GitOps job fails — caller commit gets failure status
Given the GitOps repository clone will fail
When the GitOps update script runs
Then the caller commit shows a failure status
And the script exits with error
@mock
Scenario: Everything succeeds — caller and GitOps get success
Given a valid GitOps update dispatch
When the GitOps update script runs
Then the script exits successfully
And the caller commit shows a success status with a link to the GitOps commit
And the GitOps repo commit shows a success status with a link to the caller
@mock
Scenario: GitOps push fails — both repos get failure status
Given the GitOps repo push will fail after the version is committed
When the GitOps update script runs
Then the script exits with error
And the caller commit shows a failure status
And the GitOps repo commit shows a failure status linking to the caller
@mock
Scenario: GitOps update succeeds — this repo commit status links to caller
Given a valid GitOps update dispatch
When the GitOps update script runs
Then the GitOps repo commit shows a source context status linking to the caller commit
@@ -1,10 +1,26 @@
const { execSync } = require('child_process');
const { When, Then } = require('@cucumber/cucumber');
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 {
@@ -13,7 +29,7 @@ function bash(cmd) {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
return { status: 0, stdout: out };
return { status: 0, stdout: out, stderr: '' };
} catch (e) {
return { status: e.status, stdout: e.stdout || '', stderr: e.stderr || '' };
}
@@ -35,37 +51,107 @@ function getLastPath() {
return bash(`source "${MOCK_SCRIPT}" && _get_request_file && mock_get_request_path`).stdout.trim();
}
When('a build completes successfully and dispatches a GitOps update', function () {
const 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',
].join(' ');
const r = bash(`${env} bash "${GITOPS_SCRIPT}"`);
if (r.status !== 0) throw new Error(`Expected exit 0, got ${r.status}: ${r.stderr}`);
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 = '';
});
Then('the GitOps repo has a new commit with the updated version', function () {
const out = bash(`git -C /tmp log --oneline -1 2>/dev/null || echo "no-git-log"`);
Given('the GitOps repository clone will fail', function () {
this.envOverrides = { GIT_MOCK_FAIL: '1' };
});
Then('the code repo shows a gitops status link to the GitOps commit', function () {
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":"success"')) throw new Error('Expected success status');
if (!body.includes('"context":"gitops/niko/app"')) throw new Error('Expected gitops context');
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 status path');
if (!pathStr.includes('/repos/niko/app/statuses/')) throw new Error(`Expected source repo path, got: ${pathStr}`);
});
Then('the GitOps repo shows a source status link to the code commit', function () {
const body = getLastBody();
if (!body.includes('"state":"success"')) throw new Error('Expected success status');
if (!body.includes('"context":"source/niko/app"')) throw new Error('Expected source context');
const pathStr = getLastPath();
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error('Expected gitops repo status path');
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}`);
});
+7
View File
@@ -1,6 +1,13 @@
#!/usr/bin/env bash
echo "git $*" >> "${GIT_CALLS_FILE:-/dev/null}"
[ -z "${GIT_MOCK_FAIL:-}" ] || { echo "git: mock forced failure" >&2; exit 1; }
if [ "${1:-}" = "push" ] && [ -n "${GIT_MOCK_FAIL_PUSH:-}" ]; then
echo "git: mock push failure" >&2
exit 1
fi
case "$1" in
clone)
TARGET_DIR="${@: -1}"