|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Regression test: the --json key list in pr-status.sh's header must match what |
| 3 | +# --json actually emits. |
| 4 | +# |
| 5 | +# Why this is a test and not a comment: a caller who guesses a field name gets |
| 6 | +# `null` from jq and no warning, so a watcher polling `.mergeStateStatus` |
| 7 | +# (the GraphQL name, not this script's) never fires and reads as "still |
| 8 | +# running" forever. The header exists to stop that guess — which only works |
| 9 | +# while it is true, and a documented contract that nobody checks rots at the |
| 10 | +# first new field. |
| 11 | +# |
| 12 | +# Runs pr-status.sh against a stubbed `gh`, so it needs no network and no repo. |
| 13 | + |
| 14 | +set -euo pipefail |
| 15 | + |
| 16 | +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
| 17 | +SCRIPT="$ROOT/skills/git-workflow/scripts/pr-status.sh" |
| 18 | +STUB_DIR="$(mktemp -d)" |
| 19 | +trap 'rm -rf "$STUB_DIR"' EXIT |
| 20 | + |
| 21 | +fail=0 |
| 22 | + |
| 23 | +# --- stub `gh`: one rules payload, one GraphQL payload ----------------------- |
| 24 | + |
| 25 | +printf '%s\n' '[{"type":"copilot_code_review","parameters":{}}]' > "$STUB_DIR/rules.json" |
| 26 | + |
| 27 | +cat > "$STUB_DIR/gh" <<STUB |
| 28 | +#!/usr/bin/env bash |
| 29 | +for a in "\$@"; do |
| 30 | + case "\$a" in |
| 31 | + repos/*/rules/branches/*) cat "$STUB_DIR/rules.json"; exit 0 ;; |
| 32 | + esac |
| 33 | +done |
| 34 | +cat "$STUB_DIR/graphql.json" |
| 35 | +STUB |
| 36 | +chmod +x "$STUB_DIR/gh" |
| 37 | + |
| 38 | +python3 - "$STUB_DIR/graphql.json" <<'PY' |
| 39 | +import sys, json |
| 40 | +head = "deadbeefcafe" |
| 41 | +json.dump({"data": {"repository": { |
| 42 | + "nameWithOwner": "o/r", |
| 43 | + "mergeCommitAllowed": True, "rebaseMergeAllowed": False, "squashMergeAllowed": False, |
| 44 | + "pullRequest": { |
| 45 | + "number": 1, "title": "t", "state": "OPEN", "isDraft": False, |
| 46 | + "mergeable": "MERGEABLE", "mergeStateStatus": "CLEAN", "reviewDecision": None, |
| 47 | + "author": {"login": "someone"}, |
| 48 | + "baseRefName": "main", "headRefName": "f", "headRefOid": head, |
| 49 | + "isCrossRepository": False, |
| 50 | + "reviews": {"nodes": []}, |
| 51 | + "reviewRequests": {"nodes": []}, |
| 52 | + "reviewThreads": {"nodes": []}, |
| 53 | + "commits": {"nodes": [{"commit": {"oid": head, "statusCheckRollup": { |
| 54 | + "state": "SUCCESS", "contexts": {"nodes": [ |
| 55 | + {"__typename": "CheckRun", "name": "CI", "conclusion": "SUCCESS", |
| 56 | + "status": "COMPLETED", "detailsUrl": "u", |
| 57 | + "startedAt": "2026-01-01T00:00:00Z"}]}}}}]}, |
| 58 | + "allCommits": {"nodes": [{"commit": {"oid": head, |
| 59 | + "signature": {"isValid": True}}}]}, |
| 60 | + }}}}, open(sys.argv[1], "w")) |
| 61 | +PY |
| 62 | + |
| 63 | +# --- the two lists ----------------------------------------------------------- |
| 64 | + |
| 65 | +emitted="$(PATH="$STUB_DIR:$PATH" bash "$SCRIPT" -R o/r 1 --json | jq -r 'keys[]' | sort)" |
| 66 | + |
| 67 | +# The documented block: the indented run of key names between the "--json |
| 68 | +# contract" heading and the first line that is not part of it. Matching on |
| 69 | +# indentation rather than on a marker keeps the header readable as prose. |
| 70 | +documented="$( |
| 71 | + awk ' |
| 72 | + /^# --json contract$/ { inblock = 1; next } |
| 73 | + !inblock { next } |
| 74 | + /^# [a-z]/ { sub(/^# /, ""); print; next } |
| 75 | + /^# `checks` is/ { exit } |
| 76 | + ' "$SCRIPT" | tr " " "\n" | sed "/^$/d" | sort |
| 77 | +)" |
| 78 | + |
| 79 | +# --- compare both directions ------------------------------------------------- |
| 80 | + |
| 81 | +if [ -z "$emitted" ]; then |
| 82 | + echo " FAIL the stubbed run emitted no JSON keys at all" |
| 83 | + exit 1 |
| 84 | +fi |
| 85 | +if [ -z "$documented" ]; then |
| 86 | + echo " FAIL no key list found under the '--json contract' header" |
| 87 | + exit 1 |
| 88 | +fi |
| 89 | + |
| 90 | +undocumented="$(comm -23 <(printf '%s\n' "$emitted") <(printf '%s\n' "$documented"))" |
| 91 | +stale="$(comm -13 <(printf '%s\n' "$emitted") <(printf '%s\n' "$documented"))" |
| 92 | + |
| 93 | +if [ -n "$undocumented" ]; then |
| 94 | + echo " FAIL --json emits keys the header does not list:" |
| 95 | + printf ' %s\n' "$undocumented" |
| 96 | + echo " Add them to the '--json contract' block in pr-status.sh." |
| 97 | + fail=1 |
| 98 | +else |
| 99 | + echo " ok every emitted key is documented" |
| 100 | +fi |
| 101 | + |
| 102 | +if [ -n "$stale" ]; then |
| 103 | + echo " FAIL the header lists keys --json does not emit:" |
| 104 | + printf ' %s\n' "$stale" |
| 105 | + echo " Remove them from the '--json contract' block in pr-status.sh." |
| 106 | + fail=1 |
| 107 | +else |
| 108 | + echo " ok every documented key is emitted" |
| 109 | +fi |
| 110 | + |
| 111 | +# --help must show the block; a fixed line range used to truncate it. |
| 112 | +if bash "$SCRIPT" --help | grep -q '^--json contract$'; then |
| 113 | + echo " ok --help shows the contract" |
| 114 | +else |
| 115 | + echo " FAIL --help does not show the '--json contract' block" |
| 116 | + fail=1 |
| 117 | +fi |
| 118 | + |
| 119 | +exit $fail |
0 commit comments