Compare commits

...

6 Commits

Author SHA1 Message Date
ms-lead-reviewer
571b65d336 fix(#812): resolve subpath-mounted gitea repo slug
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
2026-07-20 01:11:42 -05:00
ms-lead-reviewer
1b1902010a fix(#812): preserve configured gitea API base URL 2026-07-20 00:54:18 -05:00
ms-lead-reviewer
0115d92fda fix(#812): use supported gitea comment REST read-back 2026-07-20 00:36:48 -05:00
Hermes Agent
79988a4e13 chore(#812): WIP checkpoint — parked pending #789 terminal 2026-07-20 00:30:37 -05:00
Hermes Agent
ea7f8c5758 fix(#812): verify gitea review comment persistence 2026-07-20 00:30:37 -05:00
Hermes Agent
770e3f57ed test(#812): reproduce false-positive gitea review comment 2026-07-20 00:30:37 -05:00
5 changed files with 564 additions and 7 deletions

View File

@@ -0,0 +1,58 @@
# Issue #812 — durable Gitea PR review comments
- **Lane:** ms-812
- **Branch:** `fix/812-pr-review-comment`
- **Issue:** mosaicstack/stack#812
- **Budget:** 15K working estimate; single focused shell-wrapper/test/docs change.
## Objective
Make the Gitea `comment` action in `packages/mosaic/framework/tools/git/pr-review.sh` use the supported Gitea comments REST API and report success only after provider read-back verifies the created comment against the intended repository, PR, and exact body.
## Plan
1. Add and commit a failing shell regression harness before production changes.
2. Verify RED against the nonexistent `tea pr comment` fallback false-positive.
3. Implement the minimal supported write plus ID-based provider read-back.
4. Document that wrapper write output is not durable provenance until read-back succeeds.
5. Run focused regression tests, touched-package tests, and repository quality gates.
6. Remediate review findings, queue-guard, and push for coordinator-owned independent review. Do not open or merge a PR.
## Progress checkpoints
- [x] RED regression committed and reported to mosaic-100 (rebased commit `770e3f57`)
- [x] Initial minimal fix implemented (rebased commit `ea7f8c57`)
- [x] Rebased cleanly onto main `627cf2bb387f7c84a532d88819903a7679ce0d72`
- [x] Codex blocker remediated by replacing unsupported `tea api` with authenticated REST write/read-back
- [x] Focused, package, and repository gates green
- [ ] Coordinator-owned independent review pending after push
- [x] No PR opened; no self-review or self-merge
## Tests run
- RED after rebase: the regression harness failed against `origin/main` with status 1 after reproducing the old `tea pr comment` zero-exit fallback and false success echo.
- GREEN at resumed head: the same harness passed with REST POST 201 plus GET 200 read-back.
- All `packages/mosaic/framework/tools/git/test-*.sh` harnesses passed.
- `shellcheck -x` passed for the changed scripts; `bash -n` passed.
- Manifest resolver returned `framework` for `tools/git/test-pr-review-gitea-comment.sh`.
- `pnpm test` passed (43/43 Turbo tasks; Mosaic 75 files/1434 tests; Gateway 56 files/628 tests plus documented skips).
- `pnpm typecheck` passed (42/42 tasks), `pnpm lint` passed (23/23), and `pnpm format:check` passed.
- Firewall checks found no user-home paths or operator identities in changed shipped files; no token value is logged or echoed.
## Risks / blockers
- No active implementation blocker. #789 reached terminal merged state and the coordination hold was lifted.
- Review round 1 found one portability blocker: the API base reconstructed `https://$host` and discarded configured schemes/path prefixes.
- Review round 2 found a second subpath portability blocker: clone-derived `get_repo_slug` retained the deployment prefix, duplicating it under `/api/v1/repos/`.
- Round 3 resolves owner/repo relative to the configured Gitea base path for HTTP(S) clones while preserving root-mounted and SSH clone forms. Host matching now compares non-default ports consistently.
- REST transport failures, non-201 writes, malformed/missing created IDs, non-200 read-backs, and read-back mismatches all fail closed.
- Existing approve/request-changes behavior remains covered.
- Independent exact-head re-review remains coordinator-owned.
## Final verification evidence
- URL-portability regression was RED before remediation at the new `http://git.mosaicstack.dev` case and GREEN afterward.
- Round-3 genuine subpath regression was RED against round-2 head `1b190201` and GREEN after the fix: `https://git.example/gitea/owner/repo.git` maps to API repository `owner/repo` under configured base `/gitea`.
- Regression coverage verifies POST and read-back GET for root-mounted HTTP(S), path-prefixed HTTP(S), non-default HTTP port, scp-style SSH, and `ssh://` clone forms.
- Focused shell checks, all git-wrapper harnesses, and full repository test/typecheck/lint/format gates passed after remediation.
- Branch will be force-pushed with lease for coordinator re-verification; no PR opened.

View File

@@ -0,0 +1,9 @@
# Git provider wrappers
These scripts provide host-aware GitHub and Gitea issue, pull-request, milestone, and CI operations.
## Durable review provenance
A successful provider write command—or a wrapper message based only on that command's exit code—is **not** durable review provenance. Review comments count as durable provenance only after the wrapper reads the created provider record back and verifies that it belongs to the intended repository and pull request and contains the exact submitted body (or verifies the provider-returned record ID).
`pr-review.sh` therefore fails closed when a Gitea comment cannot be written, its created comment ID cannot be identified, or provider read-back does not match. It reports comment success only after that read-back verification passes.

View File

@@ -81,7 +81,26 @@ get_repo_slug() {
gitea_url_matches_host() { gitea_url_matches_host() {
local url="${1:-}" host="${2:-}" local url="${1:-}" host="${2:-}"
[[ -n "$url" && -n "$host" ]] || return 1 [[ -n "$url" && -n "$host" ]] || return 1
[[ "${url%/}" == "https://$host" || "${url%/}" == "http://$host" || "${url%/}" == *"//$host" ]] python3 - "$url" "$host" <<'PY'
import sys
from urllib.parse import urlparse
url, remote_host = sys.argv[1:]
configured = urlparse(url)
remote = urlparse(f"//{remote_host}")
if configured.scheme not in {"http", "https"} or configured.hostname != remote.hostname:
raise SystemExit(1)
configured_port = configured.port
remote_port = remote.port
if remote_port is None:
default_port = 80 if configured.scheme == "http" else 443
if configured_port not in {None, default_port}:
raise SystemExit(1)
elif configured_port != remote_port:
raise SystemExit(1)
raise SystemExit(0)
PY
} }
get_gitea_service_for_host() { get_gitea_service_for_host() {
@@ -347,6 +366,47 @@ get_gitea_api_host_for_repo_override() {
get_host_from_url "${GITEA_URL:-}" get_host_from_url "${GITEA_URL:-}"
} }
# Resolve owner/repo relative to a configured Gitea base URL. HTTP(S) clone
# URLs can include the deployment prefix (for example /gitea/owner/repo.git),
# but Gitea's /repos API expects only owner/repo. Root-mounted and SSH clone
# forms retain their existing owner/repo behavior.
get_gitea_repo_slug_for_url() {
local configured_url="$1" remote_url
remote_url=$(git remote get-url origin 2>/dev/null) || return 1
if [[ "$remote_url" =~ ^https?:// ]]; then
python3 - "$remote_url" "$configured_url" <<'PY'
import sys
from urllib.parse import urlparse
remote = urlparse(sys.argv[1])
base = urlparse(sys.argv[2])
remote_path = remote.path.strip("/")
if remote_path.endswith(".git"):
remote_path = remote_path[:-4]
base_path = base.path.strip("/")
remote_parts = [part for part in remote_path.split("/") if part]
base_parts = [part for part in base_path.split("/") if part]
if base_parts and remote_parts[:len(base_parts)] == base_parts:
repo_parts = remote_parts[len(base_parts):]
elif len(remote_parts) == 2:
# Preserve a root-shaped clone URL when provider API configuration carries
# a reverse-proxy prefix separately.
repo_parts = remote_parts
else:
raise SystemExit(1)
if len(repo_parts) != 2:
raise SystemExit(1)
print("/".join(repo_parts))
PY
return
fi
get_repo_slug
}
get_gitea_repo_args() { get_gitea_repo_args() {
local repo host login local repo host login
repo=$(get_repo_slug) || return 1 repo=$(get_repo_slug) || return 1
@@ -370,6 +430,11 @@ get_remote_host() {
echo "${host##*@}" echo "${host##*@}"
return 0 return 0
fi fi
if [[ "$remote_url" =~ ^ssh://([^/]+)/ ]]; then
local host="${BASH_REMATCH[1]}"
echo "${host##*@}"
return 0
fi
if [[ "$remote_url" =~ ^git@([^:]+): ]]; then if [[ "$remote_url" =~ ^git@([^:]+): ]]; then
echo "${BASH_REMATCH[1]}" echo "${BASH_REMATCH[1]}"
return 0 return 0
@@ -377,6 +442,51 @@ get_remote_host() {
return 1 return 1
} }
# Resolve the configured Gitea base URL for a host from the same credential
# source used by get_gitea_token. The scheme and any deployment path prefix are
# provider configuration and must not be reconstructed from the git remote.
get_gitea_url_for_host() {
local host="$1" script_dir cred_loader url
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cred_loader="$script_dir/../_lib/credentials.sh"
if [[ -f "$cred_loader" ]]; then
url=$(
# shellcheck source=/dev/null
source "$cred_loader"
unset GITEA_TOKEN GITEA_URL
case "$host" in
git.mosaicstack.dev) load_credentials gitea-mosaicstack 2>/dev/null ;;
git.uscllc.com) load_credentials gitea-usc 2>/dev/null ;;
*)
for svc in gitea-mosaicstack gitea-usc; do
unset GITEA_TOKEN GITEA_URL
load_credentials "$svc" 2>/dev/null || continue
if gitea_url_matches_host "${GITEA_URL:-}" "$host"; then
break
fi
unset GITEA_TOKEN GITEA_URL
done
;;
esac
if gitea_url_matches_host "${GITEA_URL:-}" "$host"; then
printf '%s' "${GITEA_URL%/}"
fi
)
if [[ -n "$url" ]]; then
printf '%s\n' "$url"
return 0
fi
fi
if gitea_url_matches_host "${GITEA_URL:-}" "$host"; then
printf '%s\n' "${GITEA_URL%/}"
return 0
fi
return 1
}
# Resolve a Gitea API token for the given host. # Resolve a Gitea API token for the given host.
# Priority: Mosaic credential loader → GITEA_TOKEN env → ~/.git-credentials # Priority: Mosaic credential loader → GITEA_TOKEN env → ~/.git-credentials
get_gitea_token() { get_gitea_token() {
@@ -403,7 +513,7 @@ get_gitea_token() {
for svc in gitea-mosaicstack gitea-usc; do for svc in gitea-mosaicstack gitea-usc; do
unset GITEA_TOKEN GITEA_URL unset GITEA_TOKEN GITEA_URL
load_credentials "$svc" 2>/dev/null || continue load_credentials "$svc" 2>/dev/null || continue
if [[ "${GITEA_URL:-}" == "https://$host" || "${GITEA_URL:-}" == "http://$host" || "${GITEA_URL:-}" == *"//$host" ]]; then if gitea_url_matches_host "${GITEA_URL:-}" "$host"; then
matched=true matched=true
break break
fi fi
@@ -423,7 +533,7 @@ get_gitea_token() {
# 2. GITEA_TOKEN env var (only when GITEA_URL, if present, matches the remote host) # 2. GITEA_TOKEN env var (only when GITEA_URL, if present, matches the remote host)
if [[ -n "${GITEA_TOKEN:-}" ]]; then if [[ -n "${GITEA_TOKEN:-}" ]]; then
if [[ -z "${GITEA_URL:-}" || "${GITEA_URL:-}" == "https://$host" || "${GITEA_URL:-}" == "http://$host" || "${GITEA_URL:-}" == *"//$host" ]]; then if [[ -z "${GITEA_URL:-}" ]] || gitea_url_matches_host "$GITEA_URL" "$host"; then
echo "$GITEA_TOKEN" echo "$GITEA_TOKEN"
return 0 return 0
fi fi

View File

@@ -5,6 +5,7 @@
set -e set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=packages/mosaic/framework/tools/git/detect-platform.sh
source "$SCRIPT_DIR/detect-platform.sh" source "$SCRIPT_DIR/detect-platform.sh"
# Parse arguments # Parse arguments
@@ -85,7 +86,10 @@ if [[ "$PLATFORM" == "github" ]]; then
elif [[ "$PLATFORM" == "gitea" ]]; then elif [[ "$PLATFORM" == "gitea" ]]; then
case $ACTION in case $ACTION in
approve) approve)
tea pr approve "$PR_NUMBER" $(get_gitea_repo_args) ${COMMENT:+--comment "$COMMENT"} repo=$(get_repo_slug)
host=$(get_remote_host)
login=$(get_gitea_login_for_host "$host")
tea pr approve "$PR_NUMBER" --repo "$repo" --login "$login" ${COMMENT:+--comment "$COMMENT"}
echo "Approved Gitea PR #$PR_NUMBER" echo "Approved Gitea PR #$PR_NUMBER"
;; ;;
request-changes) request-changes)
@@ -93,7 +97,10 @@ elif [[ "$PLATFORM" == "gitea" ]]; then
echo "Error: Comment required for request-changes" echo "Error: Comment required for request-changes"
exit 1 exit 1
fi fi
tea pr reject "$PR_NUMBER" $(get_gitea_repo_args) --comment "$COMMENT" repo=$(get_repo_slug)
host=$(get_remote_host)
login=$(get_gitea_login_for_host "$host")
tea pr reject "$PR_NUMBER" --repo "$repo" --login "$login" --comment "$COMMENT"
echo "Requested changes on Gitea PR #$PR_NUMBER" echo "Requested changes on Gitea PR #$PR_NUMBER"
;; ;;
comment) comment)
@@ -101,8 +108,103 @@ elif [[ "$PLATFORM" == "gitea" ]]; then
echo "Error: Comment required" echo "Error: Comment required"
exit 1 exit 1
fi fi
tea pr comment "$PR_NUMBER" "$COMMENT" $(get_gitea_repo_args)
echo "Added comment to Gitea PR #$PR_NUMBER" host=$(get_remote_host)
token=$(get_gitea_token "$host") || {
echo "Error: Gitea token not found for comment persistence" >&2
exit 1
}
configured_url=$(get_gitea_url_for_host "$host") || {
echo "Error: Configured Gitea URL not found for comment persistence" >&2
exit 1
}
repo=$(get_gitea_repo_slug_for_url "$configured_url") || {
echo "Error: Could not resolve Gitea owner/repository relative to configured URL" >&2
exit 1
}
api_base="${configured_url%/}/api/v1/repos/$repo"
payload=$(COMMENT_BODY="$COMMENT" python3 -c '
import json
import os
print(json.dumps({"body": os.environ["COMMENT_BODY"]}))
')
write_response_file=$(mktemp "${TMPDIR:-/tmp}/mosaic-pr-review-write.XXXXXX")
readback_response_file=$(mktemp "${TMPDIR:-/tmp}/mosaic-pr-review-readback.XXXXXX")
trap 'rm -f "$write_response_file" "$readback_response_file"' EXIT
if ! write_status=$(curl -sS -o "$write_response_file" -w '%{http_code}' \
-X POST \
-H "Authorization: token $token" \
-H 'Content-Type: application/json' \
-d "$payload" \
"$api_base/issues/$PR_NUMBER/comments"); then
echo "Error: Gitea comment write transport failed" >&2
exit 1
fi
if [[ "$write_status" != "201" ]]; then
echo "Error: Gitea comment write failed with HTTP $write_status" >&2
exit 1
fi
comment_id=$(python3 - "$write_response_file" <<'PY'
import json
import sys
try:
with open(sys.argv[1], encoding="utf-8") as response:
comment = json.load(response)
comment_id = comment.get("id") if isinstance(comment, dict) else None
if not isinstance(comment_id, int) or comment_id <= 0:
raise ValueError("missing positive comment id")
except (OSError, json.JSONDecodeError, ValueError) as error:
print(f"Error: could not identify created Gitea comment: {error}", file=sys.stderr)
raise SystemExit(1)
print(comment_id)
PY
)
if ! readback_status=$(curl -sS -o "$readback_response_file" -w '%{http_code}' \
-H "Authorization: token $token" \
"$api_base/issues/comments/$comment_id"); then
echo "Error: Gitea comment read-back transport failed" >&2
exit 1
fi
if [[ "$readback_status" != "200" ]]; then
echo "Error: Gitea comment read-back failed with HTTP $readback_status" >&2
exit 1
fi
EXPECTED_COMMENT_ID="$comment_id" EXPECTED_COMMENT_BODY="$COMMENT" EXPECTED_REPO="$repo" EXPECTED_PR_NUMBER="$PR_NUMBER" \
python3 - "$readback_response_file" <<'PY'
import json
import os
import sys
from urllib.parse import urlparse
try:
with open(sys.argv[1], encoding="utf-8") as response:
comment = json.load(response)
if not isinstance(comment, dict):
raise ValueError("response is not a comment object")
expected_id = int(os.environ["EXPECTED_COMMENT_ID"])
expected_body = os.environ["EXPECTED_COMMENT_BODY"]
expected_repo = os.environ["EXPECTED_REPO"]
expected_pr = os.environ["EXPECTED_PR_NUMBER"]
issue_path = urlparse(comment.get("issue_url", "")).path.rstrip("/")
expected_suffix = f"/repos/{expected_repo}/issues/{expected_pr}"
if comment.get("id") != expected_id:
raise ValueError("comment id mismatch")
if comment.get("body") != expected_body:
raise ValueError("comment body mismatch")
if not issue_path.endswith(expected_suffix):
raise ValueError("repository or PR mismatch")
except (OSError, json.JSONDecodeError, KeyError, TypeError, ValueError) as error:
print(f"Error: Gitea comment persistence verification failed: {error}", file=sys.stderr)
raise SystemExit(1)
PY
echo "Added and verified comment on Gitea PR #$PR_NUMBER (comment ID $comment_id)"
;; ;;
*) *)
echo "Error: Unknown action: $ACTION" echo "Error: Unknown action: $ACTION"

View File

@@ -0,0 +1,278 @@
#!/usr/bin/env bash
# Regression harness for durable Gitea PR review comments (#812).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/pr-review-gitea-comment}"
REPO_DIR="$WORK_DIR/repo"
BIN_DIR="$WORK_DIR/bin"
TEA_LOG="$WORK_DIR/tea.log"
CURL_LOG="$WORK_DIR/curl.log"
OUTPUT_FILE="$WORK_DIR/output.log"
CREDENTIALS_FILE="$WORK_DIR/credentials.json"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
mkdir -p "$REPO_DIR" "$BIN_DIR"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
write_credentials() {
local configured_url="$1"
CONFIGURED_GITEA_URL="$configured_url" python3 - "$CREDENTIALS_FILE" <<'PY'
import json
import os
import sys
with open(sys.argv[1], "w", encoding="utf-8") as credentials:
json.dump({
"gitea": {
"mosaicstack": {
"url": os.environ["CONFIGURED_GITEA_URL"],
"token": "test-only-placeholder",
}
}
}, credentials)
PY
}
cat > "$BIN_DIR/tea" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' "$*" >> "$PR_REVIEW_TEA_LOG"
if [[ "$*" == "login list --output json" ]]; then
printf '%s\n' '[{"name":"mosaicstack","url":"https://git.mosaicstack.dev"}]'
exit 0
fi
case "${PR_REVIEW_TEST_MODE:-}" in
approve)
[[ "$*" == "pr approve 123 --repo mosaicstack/stack --login mosaicstack" ]] || exit 90
;;
request-changes)
[[ "$*" == "pr reject 123 --repo mosaicstack/stack --login mosaicstack --comment changes-required" ]] || exit 91
;;
legacy-fallback|comment-success|http-success|prefix-success|subpath-success|port-success|scp-ssh-success|url-ssh-success|write-transport-failure|write-http-failure|readback-failure)
if [[ "$*" == pr\ comment* ]]; then
# tea v0.11.1 treats the nonexistent subcommand as `tea pr list` and exits 0.
printf '%s\n' 'INDEX TITLE STATE'
exit 0
fi
echo "Unexpected tea command: $*" >&2
exit 92
;;
*)
exit 95
;;
esac
SH
chmod +x "$BIN_DIR/tea"
cat > "$BIN_DIR/curl" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
output_file=""
method="GET"
payload=""
url=""
while [[ $# -gt 0 ]]; do
case "$1" in
-o)
output_file="$2"
shift 2
;;
-w|-H)
shift 2
;;
-X)
method="$2"
shift 2
;;
-d|--data)
payload="$2"
shift 2
;;
-s|-S|-sS)
shift
;;
http://*|https://*)
url="$1"
shift
;;
*)
shift
;;
esac
done
printf '%s %s\n' "$method" "$url" >> "$PR_REVIEW_CURL_LOG"
write_response() {
local status="$1" body="$2"
[[ -n "$output_file" ]] || exit 96
printf '%s' "$body" > "$output_file"
printf '%s' "$status"
}
case "${PR_REVIEW_TEST_MODE:-}" in
legacy-fallback|write-transport-failure)
echo "simulated transport failure" >&2
exit 7
;;
write-http-failure)
write_response 500 '{"message":"simulated rejection"}'
;;
comment-success|http-success|prefix-success|subpath-success|port-success|scp-ssh-success|url-ssh-success|readback-failure)
if [[ "$method" == "POST" && "$url" == "$PR_REVIEW_EXPECTED_API_BASE/issues/123/comments" ]]; then
PR_REVIEW_PAYLOAD="$payload" python3 - <<'PY'
import json
import os
assert json.loads(os.environ["PR_REVIEW_PAYLOAD"]) == {"body": os.environ["PR_REVIEW_EXPECTED_BODY"]}
PY
response=$(python3 - <<'PY'
import json
import os
print(json.dumps({"id": 456, "body": os.environ["PR_REVIEW_EXPECTED_BODY"]}))
PY
)
write_response 201 "$response"
elif [[ "$method" == "GET" && "$url" == "$PR_REVIEW_EXPECTED_API_BASE/issues/comments/456" ]]; then
if [[ "$PR_REVIEW_TEST_MODE" == "readback-failure" ]]; then
body="different-body"
else
body="$PR_REVIEW_EXPECTED_BODY"
fi
response=$(PR_REVIEW_BODY="$body" python3 - <<'PY'
import json
import os
print(json.dumps({
"id": 456,
"body": os.environ["PR_REVIEW_BODY"],
"issue_url": os.environ["PR_REVIEW_EXPECTED_API_BASE"] + "/issues/123",
}))
PY
)
write_response 200 "$response"
else
echo "Unexpected curl request: $method $url" >&2
exit 97
fi
;;
*)
exit 98
;;
esac
SH
chmod +x "$BIN_DIR/curl"
run_review() {
local mode="$1" action="$2" comment="${3:-}"
local configured_url="${4:-https://git.mosaicstack.dev}"
local remote_url="${5:-https://git.mosaicstack.dev/mosaicstack/stack.git}"
local expected_repo="${6:-mosaicstack/stack}"
local expected_api_base="${configured_url%/}/api/v1/repos/$expected_repo"
git -C "$REPO_DIR" remote set-url origin "$remote_url"
write_credentials "$configured_url"
: > "$TEA_LOG"
: > "$CURL_LOG"
: > "$OUTPUT_FILE"
(
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
PR_REVIEW_TEA_LOG="$TEA_LOG" \
PR_REVIEW_CURL_LOG="$CURL_LOG" \
PR_REVIEW_TEST_MODE="$mode" \
PR_REVIEW_EXPECTED_BODY="$comment" \
PR_REVIEW_EXPECTED_API_BASE="$expected_api_base" \
"$SCRIPT_DIR/pr-review.sh" -n 123 -a "$action" ${comment:+-c "$comment"}
) > "$OUTPUT_FILE" 2>&1
}
run_review approve approve
grep -q '^pr approve 123 --repo mosaicstack/stack --login mosaicstack$' "$TEA_LOG"
grep -q 'Approved Gitea PR #123' "$OUTPUT_FILE"
run_review request-changes request-changes changes-required
grep -q '^pr reject 123 --repo mosaicstack/stack --login mosaicstack --comment changes-required$' "$TEA_LOG"
grep -q 'Requested changes on Gitea PR #123' "$OUTPUT_FILE"
if run_review legacy-fallback comment durable-body; then
echo "The old nonexistent tea pr comment fallback returned success" >&2
cat "$OUTPUT_FILE" >&2
exit 1
fi
if grep -q '^pr comment ' "$TEA_LOG"; then
echo "Wrapper invoked unsupported tea pr comment" >&2
exit 1
fi
if grep -q 'Added comment to Gitea PR' "$OUTPUT_FILE"; then
echo "Wrapper reported success without durable persistence" >&2
exit 1
fi
complex_body=$'durable "body"\n-- marker'
run_review comment-success comment "$complex_body"
grep -q '^POST https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/issues/123/comments$' "$CURL_LOG"
grep -q '^GET https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/issues/comments/456$' "$CURL_LOG"
grep -q 'Added and verified comment on Gitea PR #123' "$OUTPUT_FILE"
if [[ -s "$TEA_LOG" ]]; then
echo "REST comment path unexpectedly invoked tea" >&2
cat "$TEA_LOG" >&2
exit 1
fi
run_review http-success comment durable-body http://git.mosaicstack.dev
grep -q '^POST http://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/issues/123/comments$' "$CURL_LOG"
grep -q '^GET http://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/issues/comments/456$' "$CURL_LOG"
run_review prefix-success comment durable-body https://git.mosaicstack.dev/gitea/
grep -q '^POST https://git.mosaicstack.dev/gitea/api/v1/repos/mosaicstack/stack/issues/123/comments$' "$CURL_LOG"
grep -q '^GET https://git.mosaicstack.dev/gitea/api/v1/repos/mosaicstack/stack/issues/comments/456$' "$CURL_LOG"
run_review subpath-success comment durable-body https://git.example/gitea https://git.example/gitea/owner/repo.git owner/repo
grep -q '^POST https://git.example/gitea/api/v1/repos/owner/repo/issues/123/comments$' "$CURL_LOG"
grep -q '^GET https://git.example/gitea/api/v1/repos/owner/repo/issues/comments/456$' "$CURL_LOG"
if grep -q '/repos/gitea/owner/repo/' "$CURL_LOG"; then
echo "Configured Gitea path prefix leaked into the repository slug" >&2
exit 1
fi
run_review port-success comment durable-body http://git.example:3000 http://git.example:3000/owner/repo.git owner/repo
grep -q '^POST http://git.example:3000/api/v1/repos/owner/repo/issues/123/comments$' "$CURL_LOG"
grep -q '^GET http://git.example:3000/api/v1/repos/owner/repo/issues/comments/456$' "$CURL_LOG"
run_review scp-ssh-success comment durable-body https://git.example git@git.example:owner/repo.git owner/repo
grep -q '^POST https://git.example/api/v1/repos/owner/repo/issues/123/comments$' "$CURL_LOG"
run_review url-ssh-success comment durable-body https://git.example ssh://git@git.example/owner/repo.git owner/repo
grep -q '^POST https://git.example/api/v1/repos/owner/repo/issues/123/comments$' "$CURL_LOG"
if run_review write-transport-failure comment durable-body; then
echo "Expected provider transport failure to return nonzero" >&2
exit 1
fi
if run_review write-http-failure comment durable-body; then
echo "Expected non-201 provider write to return nonzero" >&2
exit 1
fi
if run_review readback-failure comment durable-body; then
echo "Expected mismatched provider read-back to return nonzero" >&2
exit 1
fi
if grep -q 'Added and verified comment' "$OUTPUT_FILE"; then
echo "Read-back mismatch reported durable success" >&2
exit 1
fi
echo "pr-review.sh durable Gitea comment regression passed"