gitops init
CI Feature / Load example-gitea-env.conf to pipeline env (push) Successful in 24s
acc-tests Cucumber test report
CI Feature / Cucumber tests (push) Failing after 1m10s
unit-tests Bats test report
CI Feature / Bats tests (push) Successful in 1m35s
CI Feature / Report Summary (push) Successful in 6s

This commit is contained in:
moilanik
2026-06-21 15:44:31 +03:00
parent a5947551d4
commit 86e73d87d3
8 changed files with 388 additions and 4 deletions
+15
View File
@@ -0,0 +1,15 @@
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
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
@@ -0,0 +1,71 @@
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 GITOPS_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'gitops-update.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 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();
}
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}`);
});
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"`);
});
Then('the code repo shows a gitops status link to the GitOps commit', 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');
const pathStr = getFirstPath();
if (!pathStr.includes('/repos/niko/app/statuses/')) throw new Error('Expected source repo status path');
});
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');
});
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env bats
setup() {
export INPUT_FILE=dev/Chart.yaml
export YQ_TPL='version = "{{VERSION}}"'
export VERSION=1.0.0
export SOURCE_REPO=niko/app
export SOURCE_COMMIT=abc123def456
export GITOPS_REPO=niko/app-gitops
export GITEA_TOKEN=test-token
export GITEA_API_URL=http://localhost:18080
}
teardown() {
if type mock_stop &>/dev/null 2>&1; then
mock_stop 2>/dev/null || true
fi
}
@test "missing GITEA_API_URL causes exit 1" {
unset GITEA_API_URL
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"GITEA_API_URL"* ]]
}
@test "missing GITEA_TOKEN causes exit 1" {
unset GITEA_TOKEN
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"GITEA_TOKEN"* ]]
}
@test "missing INPUT_FILE causes exit 1" {
unset INPUT_FILE
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"INPUT_FILE"* ]]
}
@test "missing YQ_TPL causes exit 1" {
unset YQ_TPL
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"YQ_TPL"* ]]
}
@test "missing VERSION causes exit 1" {
unset VERSION
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"VERSION"* ]]
}
@test "missing SOURCE_REPO causes exit 1" {
unset SOURCE_REPO
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"SOURCE_REPO"* ]]
}
@test "missing SOURCE_COMMIT causes exit 1" {
unset SOURCE_COMMIT
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"SOURCE_COMMIT"* ]]
}
@test "_gitops_substitute replaces {{VERSION}}" {
run bash -c '
source scripts/gitops-update.sh >/dev/null 2>&1
_gitops_substitute "(.version) = \"{{VERSION}}\"" "0.2.3"
'
[ "$status" -eq 0 ]
[[ "$output" == '(.version) = "0.2.3"' ]]
}
@test "CLONE_URL is constructed correctly from GITEA_API_URL" {
export GITEA_API_URL=https://gitea.app.keskikuja.site
export GITEA_TOKEN=secret123
export GITOPS_REPO=niko/app-gitops
run bash -c '
source scripts/gitops-update.sh >/dev/null 2>&1
echo "$CLONE_URL"
'
[ "$status" -eq 0 ]
[ "$output" = "https://secret123@gitea.app.keskikuja.site/niko/app-gitops.git" ]
}
@test "CLONE_URL works with http:// URL" {
export GITEA_API_URL=http://localhost:18080
export GITEA_TOKEN=token
export GITOPS_REPO=owner/repo
run bash -c '
source scripts/gitops-update.sh >/dev/null 2>&1
echo "$CLONE_URL"
'
[ "$status" -eq 0 ]
[ "$output" = "https://token@localhost:18080/owner/repo.git" ]
}
@test "_gitops_substitute handles multiple {{VERSION}} occurrences" {
run bash -c '
source scripts/gitops-update.sh >/dev/null 2>&1
_gitops_substitute "version = \"{{VERSION}}\"; tag = \"v{{VERSION}}\"" "1.2.3"
'
[ "$status" -eq 0 ]
[[ "$output" == 'version = "1.2.3"; tag = "v1.2.3"' ]]
}
@test "git flow: clone yq add commit push" {
source tests/helpers/mock-api.sh
mock_set_sequence '[
{"code":201},
{"code":201}
]'
mock_start
export GIT_CALLS_FILE=$(mktemp)
export YQ_CALLS_FILE=$(mktemp)
export PATH="${BATS_TEST_DIRNAME}/helpers:$PATH"
export INPUT_FILE=dev/Chart.yaml
export YQ_TPL='(.version) = "{{VERSION}}"'
export VERSION=0.2.3
export SOURCE_REPO=niko/app
export SOURCE_COMMIT=abc123def456
export GITOPS_REPO=niko/app-gitops
export GITEA_API_URL=http://localhost:18080
export GITEA_TOKEN=test-token
run bash scripts/gitops-update.sh
[ "$status" -eq 0 ]
git_calls=$(cat "$GIT_CALLS_FILE")
[[ "$git_calls" == *"clone"* ]]
[[ "$git_calls" == *"add"* ]]
[[ "$git_calls" == *"commit"* ]]
[[ "$git_calls" == *"push"* ]]
yq_calls=$(cat "$YQ_CALLS_FILE")
[[ "$yq_calls" == *"eval -i"* ]]
rm -f "$GIT_CALLS_FILE" "$YQ_CALLS_FILE"
mock_stop
}
@test "two commit-status calls: code-repo and gitops-repo" {
source tests/helpers/mock-api.sh
mock_set_sequence '[
{"code":201},
{"code":201}
]'
mock_start
export GIT_CALLS_FILE=$(mktemp)
export YQ_CALLS_FILE=$(mktemp)
export PATH="${BATS_TEST_DIRNAME}/helpers:$PATH"
export INPUT_FILE=dev/Chart.yaml
export YQ_TPL='(.version) = "{{VERSION}}"'
export VERSION=0.2.3
export SOURCE_REPO=niko/app
export SOURCE_COMMIT=abc123def456
export GITOPS_REPO=niko/app-gitops
export GITEA_API_URL=http://localhost:18080
export GITEA_TOKEN=test-token
run bash scripts/gitops-update.sh
[ "$status" -eq 0 ]
path1=$(mock_get_first_request_path)
body1=$(mock_get_first_request_body)
[[ "$path1" == *"/repos/niko/app/statuses/"* ]]
[[ "$body1" == *'"context":"gitops/niko/app"'* ]]
path2=$(mock_get_request_path)
body2=$(mock_get_request_body)
[[ "$path2" == *"/repos/niko/app-gitops/statuses/"* ]]
[[ "$body2" == *'"context":"source/niko/app"'* ]]
rm -f "$GIT_CALLS_FILE" "$YQ_CALLS_FILE"
mock_stop
}
@test "missing GITOPS_REPO causes exit 1" {
unset GITOPS_REPO
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"GITOPS_REPO"* ]]
}
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
echo "git $*" >> "${GIT_CALLS_FILE:-/dev/null}"
case "$1" in
clone)
TARGET_DIR="${@: -1}"
mkdir -p "$TARGET_DIR"
cd "$TARGET_DIR"
git init --initial-branch=main 2>/dev/null
git config user.email "mock@test.com"
git config user.name "Mock Test"
mkdir -p "$(dirname "$INPUT_FILE")"
echo 'version: 0.1.0' > "$INPUT_FILE"
git add -A 2>/dev/null
git commit -m "initial" 2>/dev/null
echo "Cloning into '$TARGET_DIR'..."
;;
add|commit|push|config|init)
;;
rev-parse)
echo "mock-sha-9876543210fedcba9876543210fedcba98765432"
;;
*)
echo "git: unknown command: $*" >&2
exit 1
;;
esac
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
echo "yq $*" >> "${YQ_CALLS_FILE:-/dev/null}"