fix(tools/git): issue-close.sh silently dropped the closing comment #1085
@@ -91,13 +91,18 @@ elif [[ "$PLATFORM" == "gitea" ]]; then
|
||||
GITEA_LOGIN_NAME=$(get_gitea_login || true)
|
||||
if [[ -n "$GITEA_LOGIN_NAME" ]]; then
|
||||
if [[ -n "$COMMENT" ]]; then
|
||||
# `tea issue comment` is NOT a subcommand (tea 0.11.x lists only
|
||||
# list/create/edit/reopen/close); comments are the top-level `tea comment`.
|
||||
# The call therefore always failed, was unchecked, and the script proceeded
|
||||
# to close the issue anyway -- losing the record of WHY it was closed.
|
||||
# Route through the authenticated API helper: it is login-independent and is
|
||||
# already the mechanism used by the no-login branch below.
|
||||
gitea_issue_comment_api || {
|
||||
# `tea issue comment` is NOT a subcommand -- tea 0.11.x lists only
|
||||
# list/create/edit/reopen/close under `tea issue`. Comments are the
|
||||
# TOP-LEVEL `tea comment`, which takes the same --repo/--login flags.
|
||||
# The old call therefore always failed, was unchecked, and the script
|
||||
# closed the issue anyway, losing the record of WHY.
|
||||
#
|
||||
# Use `tea comment` rather than the API helper so the comment and the
|
||||
# close are made by the SAME principal ($GITEA_LOGIN_NAME). Routing the
|
||||
# comment through the token-authenticated helper here would attribute the
|
||||
# comment to the token holder and the close to the tea login -- two
|
||||
# principals for one operation.
|
||||
tea comment "$ISSUE_NUMBER" "$COMMENT" --repo "$OWNER/$REPO" --login "$GITEA_LOGIN_NAME" || {
|
||||
echo "Error: failed to post comment on #$ISSUE_NUMBER -- NOT closing (fail closed)." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regression: issue-close.sh must NOT close an issue when the closing comment
|
||||
# could not be posted, and comment+close must be made by ONE principal.
|
||||
#
|
||||
# Guards two defects fixed together (see #1081):
|
||||
# 1. `tea issue comment` is not a subcommand -- tea exposes comments as the
|
||||
# TOP-LEVEL `tea comment`. The old call always failed, was unchecked, and
|
||||
# the issue closed anyway, losing the record of WHY it was closed.
|
||||
# 2. Routing the comment through the token-authenticated API helper while the
|
||||
# close used --login would attribute one operation to two principals.
|
||||
#
|
||||
# Fully offline: `tea` and `curl` are mocked onto PATH, the repo is a throwaway
|
||||
# `git init`, everything lives under $SANDBOX, removed on EXIT.
|
||||
set -uo pipefail
|
||||
|
||||
WORK_ROOT="${AGENT_WORK_ROOT:-${TMPDIR:-/tmp}}"
|
||||
SANDBOX="$WORK_ROOT/issue-close-fail-closed-test-$$"
|
||||
MOCK_BIN="$SANDBOX/bin"; REPO_DIR="$SANDBOX/repo"; CALLS="$SANDBOX/calls.log"
|
||||
cleanup() { rm -rf "$SANDBOX"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TARGET="$SCRIPT_DIR/issue-close.sh"
|
||||
[ -f "$TARGET" ] || { echo "FAIL: issue-close.sh not found beside this test"; exit 1; }
|
||||
fail() { echo "FAIL: $*"; exit 1; }
|
||||
|
||||
mkdir -p "$MOCK_BIN" "$REPO_DIR"; : > "$CALLS"
|
||||
cd "$REPO_DIR"; git init -q
|
||||
git remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
|
||||
export PATH="$MOCK_BIN:$PATH" CALLS
|
||||
export GITEA_LOGIN="git.mosaicstack.dev"
|
||||
export GITEA_URL="https://git.mosaicstack.dev"
|
||||
export GITEA_TOKEN="redacted-test-token"
|
||||
|
||||
cat > "$MOCK_BIN/curl" <<'EOF'
|
||||
#!/bin/bash
|
||||
printf 'curl %q ' "$@" >> "$CALLS"; printf '\n' >> "$CALLS"; exit 0
|
||||
EOF
|
||||
chmod +x "$MOCK_BIN/curl"
|
||||
|
||||
mk_tea() { # $1 = exit code for `tea comment`
|
||||
cat > "$MOCK_BIN/tea" <<EOF
|
||||
#!/bin/bash
|
||||
printf 'tea %s\n' "\$*" >> "$CALLS"
|
||||
if [[ "\$*" == *"login list"* ]]; then
|
||||
echo '[{"name":"git.mosaicstack.dev","url":"https://git.mosaicstack.dev"}]'; exit 0
|
||||
fi
|
||||
# Fail ANY comment attempt -- both the correct top-level \`tea comment\` and the
|
||||
# broken \`tea issue comment\` -- so the test exercises the DEFECT on an unfixed
|
||||
# script rather than failing on a setup assertion.
|
||||
if [[ "\$1" == "comment" || ( "\$1" == "issue" && "\$2" == "comment" ) ]]; then exit $1; fi
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$MOCK_BIN/tea"
|
||||
}
|
||||
|
||||
# ── 1. NEGATIVE (the regression): comment fails => must NOT close, must exit non-zero
|
||||
mk_tea 1; : > "$CALLS"
|
||||
bash "$TARGET" -i 42 -c "closing note" >/dev/null 2>&1; rc=$?
|
||||
grep -qE 'tea (issue )?comment' "$CALLS" || fail "no comment attempt at all -- setup did not reach the tea branch"
|
||||
grep -q 'tea issue close' "$CALLS" && fail "ISSUE CLOSED AFTER THE COMMENT FAILED -- the regression"
|
||||
[ "$rc" -ne 0 ] || fail "comment failed but issue-close exited 0 -- FAIL-OPEN"
|
||||
|
||||
# ── 2. POSITIVE (rule MET must pass): comment succeeds => close proceeds, exit 0
|
||||
mk_tea 0; : > "$CALLS"
|
||||
bash "$TARGET" -i 42 -c "closing note" >/dev/null 2>&1; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "comment succeeded but issue-close exited $rc"
|
||||
grep -q 'tea issue close' "$CALLS" || fail "issue not closed even though the comment succeeded"
|
||||
|
||||
# ── 3. must use top-level `tea comment`, never `tea issue comment`
|
||||
grep -q 'tea issue comment' "$CALLS" && fail "used 'tea issue comment' -- not a valid subcommand"
|
||||
|
||||
# ── 4. ONE PRINCIPAL: comment and close must carry the SAME --login
|
||||
c=$(grep -m1 '^tea comment' "$CALLS" | grep -o -- '--login [^ ]*' | awk '{print $2}')
|
||||
k=$(grep -m1 '^tea issue close' "$CALLS" | grep -o -- '--login [^ ]*' | awk '{print $2}')
|
||||
[ -n "$c" ] || fail "comment carried no --login"
|
||||
[ "$c" = "$k" ] || fail "MIXED PRINCIPALS: comment=$c close=$k"
|
||||
|
||||
echo "issue-close.sh fail-closed + single-principal regression passed"
|
||||
Reference in New Issue
Block a user