#!/usr/bin/env bash # Regression harness for ci-queue-wait.sh's --no-ci-expected assertion: # the sanctioned merge path for a repository with no CI configured at all. # # Zero status contexts ("no-status") stays fail-closed for --purpose merge # by default, because at merge time no-status can also mean "CI has not # reported yet". --no-ci-expected reclassifies ONLY that zero-context case # as queue-clear, and only for a caller whose acting token holds repository # admin. This harness pins: # (a) merge + no-status + flag + admin -> exit 0, audit line + JSONL. # (b) merge + no-status, no flag -> exit 3, existing text (unchanged). # (c) merge + no-status + flag + non-admin -> exit 77 ASSERTION_REFUSED # (distinct text, exit code NOT 3) + JSONL refusal record. # (c2) flag + admin payload without the admin field -> fail closed as (c). # (d) flag + --require-status -> usage error, before any network. # (e) flag + a real pending context -> still holds (timeout 124), # and the admin endpoint is never consulted. # (f) push + no-status, with and without the flag -> push queue-clear # unchanged; no admin consultation on push. # (g) flag + admin lookup unreachable -> CANNOT_ASSERT hold (75), # not a silent pass and not a refusal. # (h) flag + admin stub + NO MOSAIC_GIT_IDENTITY -> refusal BEFORE # queue-clear and BEFORE the admin lookup: exit 78, no queue-clear # line, an ASSERTION_UNATTRIBUTABLE JSONL record, no repos/ call. set -u SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/ci-queue-wait-no-ci-expected}" REPO_DIR="$WORK_DIR/repo" STUB_DIR="$WORK_DIR/stubs" URL_LOG="$WORK_DIR/urls.log" rm -rf "$WORK_DIR" mkdir -p "$REPO_DIR" "$STUB_DIR" git -C "$REPO_DIR" init -q git -C "$REPO_DIR" remote add origin https://git.example.test/acme/widgets.git # Same stub conventions as test-ci-queue-wait-no-status.sh; adds the # repository-object endpoint (admin state) selected by MOSAIC_STUB_ADMIN_MODE. cat > "$STUB_DIR/curl" <<'SH' #!/usr/bin/env bash set -euo pipefail has_w=0 url="" for arg in "$@"; do case "$arg" in -w) has_w=1 ;; http://*|https://*) url="$arg" ;; esac done printf '%s\n' "$url" >> "${MOSAIC_STUB_URL_LOG:?}" case "$url" in */branches/*) body='{"commit":{"id":"deadbeefcafef00d0123456789abcdef01234567"}}' if [[ "$has_w" == 1 ]]; then printf '%s\n200' "$body" else printf '%s' "$body" fi exit 0 ;; */status) mode="${MOSAIC_STUB_STATUS_MODE:?MOSAIC_STUB_STATUS_MODE not set}" case "$mode" in no-status) body='{"state":"","statuses":[]}' ;; real-pending) body='{"state":"pending","statuses":[{"context":"ci/woodpecker","status":"running","target_url":""}]}' ;; *) echo "curl stub: unknown status mode=$mode" >&2; exit 2 ;; esac printf '%s' "$body" exit 0 ;; */repos/*) mode="${MOSAIC_STUB_ADMIN_MODE:?MOSAIC_STUB_ADMIN_MODE not set}" case "$mode" in admin) body='{"permissions":{"admin":true,"push":true,"pull":true}}' ;; non-admin) body='{"permissions":{"admin":false,"push":true,"pull":true}}' ;; no-admin-field) body='{"permissions":{}}' ;; unreachable) exit 7 ;; *) echo "curl stub: unknown admin mode=$mode" >&2; exit 2 ;; esac if [[ "$has_w" == 1 ]]; then printf '%s\n200' "$body" else printf '%s' "$body" fi exit 0 ;; *) echo "curl stub: unrecognized URL: $url" >&2 exit 2 ;; esac SH chmod +x "$STUB_DIR/curl" failures=0 run_guard() { local name="$1"; shift ( cd "$REPO_DIR" || exit export PATH="$STUB_DIR:$PATH" export MOSAIC_CREDENTIALS_FILE="$WORK_DIR/no-credentials.json" export MOSAIC_CI_QUEUE_AUDIT_LOG="$WORK_DIR/audit-$name.jsonl" export MOSAIC_STUB_URL_LOG="$URL_LOG" export GITEA_TOKEN="stub-token" export GITEA_URL="https://git.example.test" export MOSAIC_GIT_IDENTITY="test-identity" "$SCRIPT_DIR/ci-queue-wait.sh" -B main -t 3 -i 1 "$@" ) } # The suite exports test-identity globally, so the unattributable-caller # case must strip it from the child environment at invocation with env -u, # not rely on the export order. run_guard_no_identity() { local name="$1"; shift ( cd "$REPO_DIR" || exit export PATH="$STUB_DIR:$PATH" export MOSAIC_CREDENTIALS_FILE="$WORK_DIR/no-credentials.json" export MOSAIC_CI_QUEUE_AUDIT_LOG="$WORK_DIR/audit-$name.jsonl" export MOSAIC_STUB_URL_LOG="$URL_LOG" export GITEA_TOKEN="stub-token" export GITEA_URL="https://git.example.test" export MOSAIC_GIT_IDENTITY="test-identity" env -u MOSAIC_GIT_IDENTITY \ "$SCRIPT_DIR/ci-queue-wait.sh" -B main -t 3 -i 1 "$@" ) } expect_rc() { local name="$1" want="$2" got="$3" if [[ "$want" == "not3" ]]; then if [[ "$got" -eq 0 || "$got" -eq 3 ]]; then echo "FAIL $name: expected a refusal rc (nonzero, not 3), got $got" >&2 failures=$((failures + 1)) return 1 fi elif [[ "$got" -ne "$want" ]]; then echo "FAIL $name: expected rc=$want, got rc=$got" >&2 failures=$((failures + 1)) return 1 fi return 0 } expect_text() { local name="$1" want="$2" output="$3" polarity="${4:-present}" if [[ "$polarity" == "present" && "$output" != *"$want"* ]]; then echo "FAIL $name: output missing '$want'" >&2 printf '%s\n' "$output" >&2 failures=$((failures + 1)) elif [[ "$polarity" == "absent" && "$output" == *"$want"* ]]; then echo "FAIL $name: output unexpectedly contains '$want'" >&2 printf '%s\n' "$output" >&2 failures=$((failures + 1)) fi } repo_root_fetched() { grep -q 'repos/acme/widgets$' "$URL_LOG" } # (a) merge + no-status + flag + admin -> exit 0, assertion line, JSONL record. : > "$URL_LOG" set +e out_a=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=admin run_guard a --purpose merge --no-ci-expected 2>&1) rc_a=$? set -u if expect_rc a 0 "$rc_a"; then expect_text a "queue-clear state=no-status purpose=merge asserted-by=test-identity reason=no-ci-expected branch=main" "$out_a" expect_text a "ASSERTED_NOT_READY" "$out_a" absent if ! grep -q '"outcome":"NO_CI_ASSERTED"' "$WORK_DIR/audit-a.jsonl" 2>/dev/null; then echo "FAIL a: expected a NO_CI_ASSERTED JSONL audit record" >&2 failures=$((failures + 1)) elif ! grep -q '"asserted_by":"test-identity"' "$WORK_DIR/audit-a.jsonl"; then echo "FAIL a: audit record does not name the asserting identity" >&2 failures=$((failures + 1)) fi fi # (b) merge + no-status, no flag -> exit 3, existing error text unchanged. : > "$URL_LOG" set +e out_b=$(MOSAIC_STUB_STATUS_MODE=no-status run_guard b --purpose merge 2>&1) rc_b=$? set -u if expect_rc b 3 "$rc_b"; then expect_text b "Error: ASSERTED_NOT_READY state=no-status purpose=merge branch=main." "$out_b" expect_text b "asserted-by" "$out_b" absent fi if repo_root_fetched; then echo "FAIL b: admin endpoint consulted without the flag" >&2 failures=$((failures + 1)) fi # (c) merge + no-status + flag + non-admin -> distinct refusal, rc NOT 3. : > "$URL_LOG" set +e out_c=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=non-admin run_guard c --purpose merge --no-ci-expected 2>&1) rc_c=$? set -u if expect_rc c not3 "$rc_c"; then if [[ "$rc_c" -ne 77 ]]; then echo "FAIL c: expected the documented refusal rc=77, got $rc_c" >&2 failures=$((failures + 1)) fi expect_text c "ASSERTION_REFUSED state=no-status purpose=merge asserted-by=test-identity reason=no-ci-expected branch=main" "$out_c" expect_text c "ASSERTED_NOT_READY" "$out_c" absent if ! grep -q '"outcome":"ASSERTION_REFUSED"' "$WORK_DIR/audit-c.jsonl" 2>/dev/null; then echo "FAIL c: expected an ASSERTION_REFUSED JSONL audit record" >&2 failures=$((failures + 1)) fi fi # (c2) admin payload with no admin field -> fail closed as non-admin. : > "$URL_LOG" set +e out_c2=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=no-admin-field run_guard c2 --purpose merge --no-ci-expected 2>&1) rc_c2=$? set -u if expect_rc c2 77 "$rc_c2"; then expect_text c2 "ASSERTION_REFUSED" "$out_c2" fi # (d) flag + --require-status -> usage error before any network I/O. : > "$URL_LOG" set +e out_d=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=admin run_guard d --purpose merge --no-ci-expected --require-status 2>&1) rc_d=$? set -u if expect_rc d 1 "$rc_d"; then expect_text d "--no-ci-expected and --require-status contradict" "$out_d" fi if [[ -s "$URL_LOG" ]]; then echo "FAIL d: usage error must precede every network call" >&2 failures=$((failures + 1)) fi # (e) flag + a real pending context -> still holds; admin endpoint never asked. : > "$URL_LOG" set +e out_e=$(MOSAIC_STUB_STATUS_MODE=real-pending MOSAIC_STUB_ADMIN_MODE=admin run_guard e --purpose merge --no-ci-expected 2>&1) rc_e=$? set -u if expect_rc e 124 "$rc_e"; then expect_text e "ASSERTED_NOT_READY" "$out_e" expect_text e "ci/woodpecker=running" "$out_e" fi if repo_root_fetched; then echo "FAIL e: a pending context must not trigger the admin assertion" >&2 failures=$((failures + 1)) fi # (f) push + no-status stays queue-clear, with and without the flag. : > "$URL_LOG" set +e out_f=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=non-admin run_guard f --purpose push 2>&1) rc_f=$? set -u if expect_rc f 0 "$rc_f"; then expect_text f "queue-clear state=no-status purpose=push branch=main; no queued or running CI." "$out_f" fi : > "$URL_LOG" set +e out_f2=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=non-admin run_guard f2 --purpose push --no-ci-expected 2>&1) rc_f2=$? set -u if expect_rc f2 0 "$rc_f2"; then expect_text f2 "queue-clear state=no-status purpose=push branch=main; no queued or running CI." "$out_f2" expect_text f2 "asserted-by" "$out_f2" absent fi if repo_root_fetched; then echo "FAIL f: push must not consult the admin endpoint" >&2 failures=$((failures + 1)) fi # (g) flag + admin lookup unreachable -> CANNOT_ASSERT hold (75), not a pass. : > "$URL_LOG" set +e out_g=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=unreachable run_guard g --purpose merge --no-ci-expected 2>&1) rc_g=$? set -u if expect_rc g 75 "$rc_g"; then expect_text g "CANNOT_ASSERT reason=repo-permissions-unavailable" "$out_g" fi if ! grep -q '"outcome":"CANNOT_ASSERT"' "$WORK_DIR/audit-g.jsonl" 2>/dev/null; then echo "FAIL g: expected a CANNOT_ASSERT JSONL audit record" >&2 failures=$((failures + 1)) fi # (h) flag + admin stub + no asserting identity -> refusal before queue-clear # and before the admin lookup: rc 78, no queue-clear line, an # ASSERTION_UNATTRIBUTABLE JSONL record, and zero repos/ network calls. : > "$URL_LOG" set +e out_h=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=admin run_guard_no_identity h --purpose merge --no-ci-expected 2>&1) rc_h=$? set -u if expect_rc h 78 "$rc_h"; then expect_text h "ASSERTION_UNATTRIBUTABLE state=no-status purpose=merge asserted-by=unknown reason=no-ci-expected branch=main" "$out_h" expect_text h "queue-clear" "$out_h" absent if ! grep -q '"outcome":"ASSERTION_UNATTRIBUTABLE"' "$WORK_DIR/audit-h.jsonl" 2>/dev/null; then echo "FAIL h: expected an ASSERTION_UNATTRIBUTABLE JSONL audit record" >&2 failures=$((failures + 1)) fi fi if repo_root_fetched; then echo "FAIL h: an unattributable caller must not trigger the permission lookup" >&2 failures=$((failures + 1)) fi if [[ "$failures" -ne 0 ]]; then echo "ci-queue-wait no-ci-expected regression failed ($failures assertions)" >&2 exit 1 fi echo "ci-queue-wait no-ci-expected regression passed (all outcome classes)"