#!/usr/bin/env bash # Contract tests for lib/closes_references.sh (issue #188, term 3). # set -u, not -e: failing commands are behavior for the harness to inspect. set -u ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # shellcheck source=test/harness.sh . "$ROOT/test/harness.sh" # issue_references (the LOCAL/CROSS classifier) lives here; closes_references # calls it, exactly as refs_references does. # shellcheck source=actions/issueflow-reconcile/issueflow-reconcile.sh . "$ROOT/actions/issueflow-reconcile/issueflow-reconcile.sh" # shellcheck source=lib/closes_references.sh . "$ROOT/lib/closes_references.sh" # closes — the parse of is exactly # . Exact, not substring: "12" is contained in "123". closes() { local want="$1" body="$2" got got="$(printf '%s' "$body" | closes_references)" [ "$got" = "$want" ] } # --- the three verbs, the three tenses ---------------------------------- # GitHub's documented keyword set. All of them, because a body that says # "Fixed #4" and goes unclosed is a silent failure of the post-merge # transition, not a loud one. check "closes" 0 "" closes 1 'Closes #1' check "close" 0 "" closes 1 'Close #1' check "closed" 0 "" closes 1 'Closed #1' check "fixes" 0 "" closes 2 'Fixes #2' check "fix" 0 "" closes 2 'Fix #2' check "fixed" 0 "" closes 2 'Fixed #2' check "resolves" 0 "" closes 3 'Resolves #3' check "resolve" 0 "" closes 3 'Resolve #3' check "resolved" 0 "" closes 3 'Resolved #3' check "case-insensitive" 0 "" closes 4 'CLOSES #4' check "lowercase" 0 "" closes 4 'closes #4' check "colon form" 0 "" closes 5 'Closes: #5' # --- Refs is NOT a closing link ----------------------------------------- # The relation this file must not swallow. refs_references owns Refs, and # conflating them makes every referenced issue look closeable — the # post-merge transition #151 was reopened by hand over exactly that # distinction. check "Refs is not a close" 0 "" closes '' 'Refs #7' check "Refs and Closes in one body keeps only the close" 0 "" \ closes 8 $'Refs #7\nCloses #8' # --- cross-repo references stay out (#61) ------------------------------- # rig#112 must never be read as local #112. The classifier is shared with # refs_references precisely so this rule has one implementation. check "qualified reference is not local" 0 "" closes '' 'Closes rig#112' check "owner-qualified reference is not local" 0 "" \ closes '' 'Closes heavy-duty/rig#112' check "a local and a cross reference keep only the local" 0 "" \ closes 9 $'Closes rig#112\nCloses #9' # --- every occurrence contributes --------------------------------------- # Binding to the first occurrence is the defect #184 fixed in # blocked_reference_records; this parser must not reintroduce it. check "two closes on one line" 0 "" closes $'1\n2' 'Closes #1. Closes #2.' check "two closes on two lines" 0 "" closes $'1\n2' $'Closes #1\nCloses #2' check "sorted and deduplicated" 0 "" closes $'2\n10' $'Closes #10\nCloses #2\nCloses #10' # --- prose must not be swallowed ---------------------------------------- check "trailing prose is not part of the reference" 0 "" \ closes 12 'Closes #12, and adds the guard' check "a sentence terminator ends the reference" 0 "" closes 13 'Closes #13.' check "no reference means no output" 0 "" closes '' 'Closes the door behind it' check "a bare issue mention is not a close" 0 "" closes '' 'See #14 for context' # "unclosed" contains "close" — a naive word match would fire on it. check "a word merely containing a verb does not fire" 0 "" \ closes '' 'This left #15 unclosed' # --- the shapes a real PR body carries ---------------------------------- check "the template's leading declaration" 0 "" \ closes 188 $'Closes #188\n\n## Acceptance criteria\n\n- [ ] a thing' check "an empty body yields nothing" 0 "" closes '' '' summary