isoja testimuutoksia
CI Feature / Load example-gitea-env.conf to pipeline env (push) Successful in 22s
acc-tests Cucumber test report
CI Feature / Cucumber tests (push) Successful in 1m13s
unit-tests Bats test report
CI Feature / Bats tests (push) Successful in 1m26s
CI Feature / Report Summary (push) Successful in 5s

This commit is contained in:
moilanik
2026-06-20 14:26:08 +03:00
parent ae03589563
commit 6a113659c8
10 changed files with 50 additions and 69 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ on:
cucumber-node-image: cucumber-node-image:
required: false required: false
type: string type: string
default: gitea.app.keskikuja.site/niko/ci-cucumber:latest default: gitea.app.keskikuja.site/niko/ci-cucumber:with-python
secrets: secrets:
GITEA_TOKEN: GITEA_TOKEN:
required: true required: true
+1
View File
@@ -5,6 +5,7 @@ source "$BATS_TEST_DIRNAME/helpers/mock-api.sh"
setup() { setup() {
export GITEA_TOKEN=test-token export GITEA_TOKEN=test-token
export GIT_TAG_PREFIX="" export GIT_TAG_PREFIX=""
export SERVER_URL="http://localhost:18080"
export REPO="niko/test" export REPO="niko/test"
export SHA="abc123" export SHA="abc123"
rm -rf /tmp/build-ctx rm -rf /tmp/build-ctx
+7 -7
View File
@@ -19,7 +19,7 @@ teardown() {
{"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}} {"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}}
]' ]'
mock_start mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "$GITEA_API_URL" "test-token-abc123" run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "http://localhost:18080" "test-token-abc123"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
} }
@@ -31,7 +31,7 @@ teardown() {
{"code":200,"body":{"id":1,"status":"completed","conclusion":"failure"}} {"code":200,"body":{"id":1,"status":"completed","conclusion":"failure"}}
]' ]'
mock_start mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "$GITEA_API_URL" "test-token-abc123" run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "http://localhost:18080" "test-token-abc123"
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
} }
@@ -43,7 +43,7 @@ teardown() {
{"code":200,"body":{"id":1,"status":"completed","conclusion":"cancelled"}} {"code":200,"body":{"id":1,"status":"completed","conclusion":"cancelled"}}
]' ]'
mock_start mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "$GITEA_API_URL" "test-token-abc123" run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "http://localhost:18080" "test-token-abc123"
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
} }
@@ -61,7 +61,7 @@ teardown() {
{"code":200,"body":{"id":1,"status":"running"}} {"code":200,"body":{"id":1,"status":"running"}}
]' ]'
mock_start mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "$GITEA_API_URL" "test-token-abc123" "0.001" run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "http://localhost:18080" "test-token-abc123" "0.001"
[ "$status" -eq 124 ] [ "$status" -eq 124 ]
} }
@@ -70,7 +70,7 @@ teardown() {
{"code":500} {"code":500}
]' ]'
mock_start mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "$GITEA_API_URL" "test-token-abc123" run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "http://localhost:18080" "test-token-abc123"
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
} }
@@ -81,7 +81,7 @@ teardown() {
{"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}} {"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}}
]' ]'
mock_start mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "$GITEA_API_URL" "test-token-abc123" run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "http://localhost:18080" "test-token-abc123"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
path=$(mock_get_first_request_path) path=$(mock_get_first_request_path)
[[ "$path" == *"/api/v1/repos/test-owner/test-repo/actions/workflows/test.yml/dispatches"* ]] [[ "$path" == *"/api/v1/repos/test-owner/test-repo/actions/workflows/test.yml/dispatches"* ]]
@@ -126,7 +126,7 @@ teardown() {
{"code":200,"body":{"workflow_runs":[]}} {"code":200,"body":{"workflow_runs":[]}}
]' ]'
mock_start mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{}' "$GITEA_API_URL" "test-token-abc123" run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{}' "http://localhost:18080" "test-token-abc123"
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
[[ "$output" == *"ERROR"* ]] [[ "$output" == *"ERROR"* ]]
} }
@@ -27,8 +27,7 @@ function bashQuiet(cmd) {
} }
function runReportStatus(args) { function runReportStatus(args) {
const apiUrl = process.env.GITEA_API_URL || 'http://localhost:18080'; 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}`);
return bash(`export GITEA_API_URL="${apiUrl}" 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() { function getMockBody() {
@@ -111,12 +110,7 @@ Then('the source commit shows the deployment status alongside the build status',
When('a build step tries to report status but the build system is unavailable', function () { When('a build step tries to report status but the build system is unavailable', function () {
bashQuiet(`source "${MOCK_SCRIPT}" && mock_stop`); bashQuiet(`source "${MOCK_SCRIPT}" && mock_stop`);
execSync('sleep 0.3', { stdio: 'ignore' }); execSync('sleep 0.3', { stdio: 'ignore' });
const out = execSync(`bash -o pipefail -c 'source "${MOCK_SCRIPT}" && mock_set_response 500 && mock_start >&2 && echo "$GITEA_API_URL"'`, { bashQuiet(`source "${MOCK_SCRIPT}" && mock_set_response 500 && mock_start`);
cwd: PROJECT_ROOT,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
process.env.GITEA_API_URL = out.trim();
const r = runReportStatus('success "Should fail"'); const r = runReportStatus('success "Should fail"');
this.reportStatusFailed = (r.status !== 0); this.reportStatusFailed = (r.status !== 0);
}); });
@@ -6,16 +6,15 @@ const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
const MOCK_SCRIPT = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-api.sh'); const MOCK_SCRIPT = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-api.sh');
Before({ tags: '@mock' }, function () { Before({ tags: '@mock' }, function () {
const out = execSync(`bash -o pipefail -c 'source "${MOCK_SCRIPT}" && mock_start >&2 && echo "$GITEA_API_URL"'`, { const out = execSync(`bash -c 'source "${MOCK_SCRIPT}" && mock_start && sleep 1 && curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health/check'`, {
cwd: PROJECT_ROOT, cwd: PROJECT_ROOT,
encoding: 'utf-8', encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
}); });
const apiUrl = out.trim(); const trimmed = out.trim();
if (!apiUrl.startsWith('http')) { if (!trimmed.startsWith('2') && !trimmed.startsWith('4')) {
throw new Error(`Mock server failed to start (no API URL: ${apiUrl})`); throw new Error(`Mock server failed to start (HTTP ${trimmed})`);
} }
process.env.GITEA_API_URL = apiUrl;
}); });
After({ tags: '@mock' }, function () { After({ tags: '@mock' }, function () {
@@ -21,15 +21,9 @@ function bash(cmd) {
} }
} }
function getFreePort() {
const out = execSync(`python3 -c "import socket; s=socket.socket(); s.bind(('',0)); print(s.getsockname()[1]); s.close()"`, { encoding: 'utf-8' });
return parseInt(out.trim(), 10);
}
function setupMock(seqJson) { function setupMock(seqJson) {
const port = getFreePort(); execSync('lsof -ti :18080 2>/dev/null | xargs -r kill -9 2>/dev/null || true', { stdio: 'ignore' });
process.env.MOCK_PORT = String(port); execSync('sleep 0.4', { stdio: 'ignore' });
process.env.GITEA_API_URL = `http://localhost:${port}`;
const seqFile = path.join(os.tmpdir(), `cucumber_seq_${Date.now()}.json`); const seqFile = path.join(os.tmpdir(), `cucumber_seq_${Date.now()}.json`);
fs.writeFileSync(seqFile, seqJson); fs.writeFileSync(seqFile, seqJson);
@@ -40,16 +34,17 @@ function setupMock(seqJson) {
const configFile = path.join(os.tmpdir(), `cucumber_cfg_${Date.now()}.txt`); const configFile = path.join(os.tmpdir(), `cucumber_cfg_${Date.now()}.txt`);
fs.writeFileSync(configFile, `SEQUENCE\n${seqJson}\n${idxFile}`); fs.writeFileSync(configFile, `SEQUENCE\n${seqJson}\n${idxFile}`);
const proc = spawn('python3', [MOCK_SERVER, String(port), configFile, reqFile], { const proc = spawn('python3', [MOCK_SERVER, '18080', configFile, reqFile], {
cwd: PROJECT_ROOT, cwd: PROJECT_ROOT,
detached: true, detached: true,
stdio: 'ignore', stdio: 'ignore',
}); });
proc.unref(); proc.unref();
this._mockProc = proc;
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
try { try {
execSync(`nc -z localhost ${port}`, { stdio: 'ignore' }); execSync('curl -s --max-time 1 http://localhost:18080/', { stdio: 'ignore' });
fs.writeFileSync(idxFile, '0'); fs.writeFileSync(idxFile, '0');
return; return;
} catch (_) {} } catch (_) {}
@@ -74,8 +69,7 @@ When('a test workflow is dispatched to a test project', function () {
{ code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } }, { code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } },
{ code: 200, body: { id: 1, status: 'completed', conclusion: 'success' } }, { code: 200, body: { id: 1, status: 'completed', conclusion: 'success' } },
])); ]));
const url = process.env.GITEA_API_URL; const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"');
const r = runDispatch(`"test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "${url}" "test-token-abc123"`);
this.dispatchResult = r.status; this.dispatchResult = r.status;
}); });
@@ -93,8 +87,7 @@ When('a test workflow is dispatched and the tests fail', function () {
{ code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } }, { code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } },
{ code: 200, body: { id: 1, status: 'completed', conclusion: 'failure' } }, { code: 200, body: { id: 1, status: 'completed', conclusion: 'failure' } },
])); ]));
const url = process.env.GITEA_API_URL; const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"');
const r = runDispatch(`"test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "${url}" "test-token-abc123"`);
this.dispatchResult = r.status; this.dispatchResult = r.status;
}); });
@@ -110,8 +103,7 @@ When('a test workflow is dispatched but does not finish within the allowed time'
{ code: 200, body: { id: 1, status: 'running' } }, { code: 200, body: { id: 1, status: 'running' } },
{ code: 200, body: { id: 1, status: 'running' } }, { code: 200, body: { id: 1, status: 'running' } },
])); ]));
const url = process.env.GITEA_API_URL; const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123" "0.001"');
const r = runDispatch(`"test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "${url}" "test-token-abc123" "0.001"`);
this.dispatchResult = r.status; this.dispatchResult = r.status;
}); });
+23 -16
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
MOCK_PORT="" MOCK_PORT=18080
MOCK_PID="" MOCK_PID=""
MOCK_REQUEST_FILE="" MOCK_REQUEST_FILE=""
MOCK_RESPONSE_CODE=201 MOCK_RESPONSE_CODE=201
@@ -9,20 +9,29 @@ MOCK_STATE_FILE="/tmp/mock_api_state"
MOCK_SEQUENCE_FILE="" MOCK_SEQUENCE_FILE=""
MOCK_CONFIG_FILE="" MOCK_CONFIG_FILE=""
_free_port() { _kill_port() {
python3 -c "import socket; s=socket.socket(); s.bind(('',0)); print(s.getsockname()[1]); s.close()" local pids
pids=$(lsof -ti ":$MOCK_PORT" 2>/dev/null) || true
if [ -n "$pids" ]; then
kill -9 $pids 2>/dev/null || true
sleep 0.5
fi
}
_wait_port_free() {
local i=0
while lsof -ti ":$MOCK_PORT" >/dev/null 2>&1 && [ $i -lt 50 ]; do
sleep 0.1
i=$((i + 1))
done
} }
_wait_port_ready() { _wait_port_ready() {
local i=0 local i=0
while [ $i -lt 30 ]; do while ! lsof -ti ":$MOCK_PORT" >/dev/null 2>&1 && [ $i -lt 30 ]; do
if nc -z localhost "$MOCK_PORT" 2>/dev/null; then sleep 0.2
return 0
fi
sleep 0.1
i=$((i + 1)) i=$((i + 1))
done done
return 1
} }
mock_set_sequence() { mock_set_sequence() {
@@ -36,12 +45,6 @@ mock_clear_sequence() {
} }
mock_start() { mock_start() {
MOCK_PORT=$(_free_port)
export MOCK_PORT
MOCK_URL="http://localhost:${MOCK_PORT}"
export SERVER_URL="$MOCK_URL"
export GITEA_API_URL="$MOCK_URL"
MOCK_RESPONSE_CODE="${MOCK_RESPONSE_CODE:-201}" MOCK_RESPONSE_CODE="${MOCK_RESPONSE_CODE:-201}"
MOCK_REQUEST_FILE=$(mktemp) MOCK_REQUEST_FILE=$(mktemp)
echo "$MOCK_REQUEST_FILE" > "$MOCK_STATE_FILE" echo "$MOCK_REQUEST_FILE" > "$MOCK_STATE_FILE"
@@ -56,15 +59,19 @@ mock_start() {
echo "$MOCK_RESPONSE_CODE" >> "$MOCK_CONFIG_FILE" echo "$MOCK_RESPONSE_CODE" >> "$MOCK_CONFIG_FILE"
fi fi
_kill_port
_wait_port_free
nohup python3 "$(dirname "${BASH_SOURCE[0]}")/mock-server.py" "$MOCK_PORT" "$MOCK_CONFIG_FILE" "$MOCK_REQUEST_FILE" \ nohup python3 "$(dirname "${BASH_SOURCE[0]}")/mock-server.py" "$MOCK_PORT" "$MOCK_CONFIG_FILE" "$MOCK_REQUEST_FILE" \
</dev/null >/dev/null 2>&1 & </dev/null >/dev/null 2>&1 &
disown
MOCK_PID=$! MOCK_PID=$!
_wait_port_ready _wait_port_ready
} }
mock_stop() { mock_stop() {
[ -n "${MOCK_PID:-}" ] && kill -9 "$MOCK_PID" 2>/dev/null || true [ -n "${MOCK_PID:-}" ] && kill -9 "$MOCK_PID" 2>/dev/null || true
_kill_port
_wait_port_free
[ -n "${MOCK_REQUEST_FILE:-}" ] && rm -f "${MOCK_REQUEST_FILE}" 2>/dev/null || true [ -n "${MOCK_REQUEST_FILE:-}" ] && rm -f "${MOCK_REQUEST_FILE}" 2>/dev/null || true
[ -n "${MOCK_SEQUENCE_FILE:-}" ] && rm -f "${MOCK_SEQUENCE_FILE}" 2>/dev/null || true [ -n "${MOCK_SEQUENCE_FILE:-}" ] && rm -f "${MOCK_SEQUENCE_FILE}" 2>/dev/null || true
[ -n "${MOCK_SEQUENCE_FILE:-}" ] && rm -f "${MOCK_SEQUENCE_FILE}.idx" 2>/dev/null || true [ -n "${MOCK_SEQUENCE_FILE:-}" ] && rm -f "${MOCK_SEQUENCE_FILE}.idx" 2>/dev/null || true
-7
View File
@@ -1,13 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import http.server, json, sys, os, threading import http.server, json, sys, os, threading
# Daemonize: detach from parent process group
if os.fork() > 0:
sys.exit(0)
os.setsid()
if os.fork() > 0:
sys.exit(0)
PORT = int(sys.argv[1]) PORT = int(sys.argv[1])
CONFIG = sys.argv[2] CONFIG = sys.argv[2]
REQ_FILE = sys.argv[3] REQ_FILE = sys.argv[3]
+2 -5
View File
@@ -63,10 +63,9 @@ teardown() {
{"code":200,"body":"published"} {"code":200,"body":"published"}
]' ]'
mock_start mock_start
export GIT_PAGES_URL="http://localhost:${MOCK_PORT}"
run bash scripts/publish-git-pages.sh "unit-tests" run bash scripts/publish-git-pages.sh "unit-tests"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[[ "$output" == "${GIT_PAGES_URL}/test-owner/test-repo/reports/abc123de" ]] [[ "$output" == "http://localhost:18080/test-owner/test-repo/reports/abc123de" ]]
} }
@test "publish with suite subpath" { @test "publish with suite subpath" {
@@ -76,10 +75,9 @@ teardown() {
{"code":200,"body":"published"} {"code":200,"body":"published"}
]' ]'
mock_start mock_start
export GIT_PAGES_URL="http://localhost:${MOCK_PORT}"
run bash scripts/publish-git-pages.sh "sub/suite" run bash scripts/publish-git-pages.sh "sub/suite"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[[ "$output" == "${GIT_PAGES_URL}/test-owner/test-repo/reports/abc123de" ]] [[ "$output" == "http://localhost:18080/test-owner/test-repo/reports/abc123de" ]]
} }
@test "git-pages returns HTTP 500 → exit 1" { @test "git-pages returns HTTP 500 → exit 1" {
@@ -87,7 +85,6 @@ teardown() {
{"code":500,"body":"internal error"} {"code":500,"body":"internal error"}
]' ]'
mock_start mock_start
export GIT_PAGES_URL="http://localhost:${MOCK_PORT}"
run bash scripts/publish-git-pages.sh "unit-tests" run bash scripts/publish-git-pages.sh "unit-tests"
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
[[ "$output" == *"500"* ]] [[ "$output" == *"500"* ]]
+2 -4
View File
@@ -23,8 +23,7 @@ teardown() {
body=$(mock_get_request_body) body=$(mock_get_request_body)
[[ "$body" == *'"state":"pending"'* ]] [[ "$body" == *'"state":"pending"'* ]]
[[ "$body" == *'"description":"Building project"'* ]] [[ "$body" == *'"description":"Building project"'* ]]
expected_url="${GITEA_API_URL}/test-owner/test-repo/actions/runs/42" [[ "$body" == *'"target_url":"http://localhost:18080/test-owner/test-repo/actions/runs/42"'* ]]
[[ "$body" == *"\"target_url\":\"${expected_url}\""* ]]
method=$(mock_get_request_method) method=$(mock_get_request_method)
[[ "$method" == "POST" ]] [[ "$method" == "POST" ]]
} }
@@ -45,8 +44,7 @@ teardown() {
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
body=$(mock_get_request_body) body=$(mock_get_request_body)
[[ "$body" == *'"state":"failure"'* ]] [[ "$body" == *'"state":"failure"'* ]]
expected_url="${GITEA_API_URL}/test-owner/test-repo/actions/runs/42" [[ "$body" == *'"target_url":"http://localhost:18080/test-owner/test-repo/actions/runs/42"'* ]]
[[ "$body" == *"\"target_url\":\"${expected_url}\""* ]]
} }
@test "default key when not provided" { @test "default key when not provided" {