#!/usr/bin/env bash # Contract tests for lib/facts.sh (issue #9) — the merge door's impure half. # Constructed git repos stand in for the consumer checkout, and a gh stub on # PATH stands in for the API, so every fact row is proven offline — # including the rows that must NOT touch the API at all (the stub's default # mode fails the test if gh is called). 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" FACTS="$ROOT/lib/facts.sh" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT ZEROS="0000000000000000000000000000000000000000" # The gh stub: behavior selected per call site by GH_STUB. The default # refuses — the -dev rows and every ordinary merge must never call the API. mkdir -p "$TMP/stub" cat >"$TMP/stub/gh" <<'EOF' #!/usr/bin/env bash case "${GH_STUB:-none}" in labeled-yes | labeled-no) if [ "$1" != api ]; then echo "gh stub: expected an api call, got: gh $*" >&2 exit 97 fi [ "${GH_STUB}" = labeled-yes ] && echo true || echo false ;; released-yes | released-no) if [ "$1" != release ]; then echo "gh stub: expected a release call, got: gh $*" >&2 exit 97 fi [ "${GH_STUB}" = released-yes ] && exit 0 || exit 1 ;; *) echo "gh stub: gh must not be called in this state (gh $*)" >&2 exit 97 ;; esac EOF chmod +x "$TMP/stub/gh" # repo — a fixture git repo; commit [file content...] repo() { git init -q "$TMP/$1" git -C "$TMP/$1" config user.email fixture@example.invalid git -C "$TMP/$1" config user.name fixture } # commit — write one file, commit, print the sha. commit() { printf '%s\n' "$3" >"$TMP/$1/$2" git -C "$TMP/$1" add "$2" git -C "$TMP/$1" commit -qm "set $2" git -C "$TMP/$1" rev-parse HEAD } # facts_in — run facts.sh inside the fixture # with the stub first on PATH. facts_in() { local dir="$1" shift (cd "$TMP/$dir" \ && env PATH="$TMP/stub:$PATH" GITHUB_REPOSITORY=fixture/fixture GH_TOKEN=stub \ "$@" bash "$FACTS") } # --- the ceremony transition (bare, changed → labeled consulted) ------------- repo ceremony base_sha="$(commit ceremony VERSION 0.6.9-dev)" head_sha="$(commit ceremony VERSION 0.7.0)" check "transition: ver is the head's" 0 "ver=0.7.0" \ facts_in ceremony VERSION_SOURCE=file MERGE_SHA="$head_sha" EVENT_BEFORE="$base_sha" GH_STUB=labeled-yes check "transition: base_ver is the base's" 0 "base_ver=0.6.9-dev" \ facts_in ceremony VERSION_SOURCE=file MERGE_SHA="$head_sha" EVENT_BEFORE="$base_sha" GH_STUB=labeled-yes check "transition: labeled=yes from the API" 0 "labeled=yes" \ facts_in ceremony VERSION_SOURCE=file MERGE_SHA="$head_sha" EVENT_BEFORE="$base_sha" GH_STUB=labeled-yes # The quoted stderr summary is the emptiness assertion: released='' can # only appear when the API was genuinely skipped (a call would have set # yes/no, or tripped the stub's default refusal). check "transition: released stays empty (not consulted)" 0 "released=''" \ facts_in ceremony VERSION_SOURCE=file MERGE_SHA="$head_sha" EVENT_BEFORE="$base_sha" GH_STUB=labeled-yes check "transition: labeled=no from the API" 0 "labeled=no" \ facts_in ceremony VERSION_SOURCE=file MERGE_SHA="$head_sha" EVENT_BEFORE="$base_sha" GH_STUB=labeled-no # event.before all-zeros (branch-create push) and empty (non-push caller) # both fall back to the head's first parent (#1 constraint 10). check "all-zeros event.before falls back to the first parent" 0 "base_ver=0.6.9-dev" \ facts_in ceremony VERSION_SOURCE=file MERGE_SHA="$head_sha" EVENT_BEFORE="$ZEROS" GH_STUB=labeled-yes check "empty event.before falls back to the first parent" 0 "base_ver=0.6.9-dev" \ facts_in ceremony VERSION_SOURCE=file MERGE_SHA="$head_sha" EVENT_BEFORE= GH_STUB=labeled-yes # --- the -dev rows: no API call, ever (the stub would exit 97) --------------- repo dev-work dev_base="$(commit dev-work VERSION 0.7.1-dev)" dev_head="$(commit dev-work notes.txt "ordinary work")" check "-dev unchanged: no API calls made" 0 "base_ver=0.7.1-dev" \ facts_in dev-work VERSION_SOURCE=file MERGE_SHA="$dev_head" EVENT_BEFORE="$dev_base" check "-dev unchanged: released and labeled stay empty" 0 "released='' labeled=''" \ facts_in dev-work VERSION_SOURCE=file MERGE_SHA="$dev_head" EVENT_BEFORE="$dev_base" repo dev-bump bump_base="$(commit dev-bump VERSION 0.7.0)" bump_head="$(commit dev-bump VERSION 0.7.1-dev)" check "the post-release bump (bare -> -dev): no API calls" 0 "ver=0.7.1-dev" \ facts_in dev-bump VERSION_SOURCE=file MERGE_SHA="$bump_head" EVENT_BEFORE="$bump_base" # --- bare, unchanged → released consulted, labeled skipped ------------------- repo window win_base="$(commit window VERSION 0.7.0)" win_head="$(commit window notes.txt "post-release window work")" check "bare unchanged: released=yes from the API" 0 "released=yes" \ facts_in window VERSION_SOURCE=file MERGE_SHA="$win_head" EVENT_BEFORE="$win_base" GH_STUB=released-yes check "bare unchanged: released=no from the API" 0 "released=no" \ facts_in window VERSION_SOURCE=file MERGE_SHA="$win_head" EVENT_BEFORE="$win_base" GH_STUB=released-no check "bare unchanged: labeled stays empty (not consulted)" 0 "labeled=''" \ facts_in window VERSION_SOURCE=file MERGE_SHA="$win_head" EVENT_BEFORE="$win_base" GH_STUB=released-yes # --- a base tree with no version source at all ------------------------------- # The merge that ADDS the version machinery (a consumer's adoption PR). # "(none)" is not a version, so decide still governs: -dev head is work, # bare head still demands the label. Nothing releases silently. repo adoption adopt_base="$(commit adoption README.md "pre-ceremony tree")" adopt_head="$(commit adoption VERSION 0.1.0-dev)" check "absent-at-base reads as (none), -dev head consults no API" 0 "base_ver=(none)" \ facts_in adoption VERSION_SOURCE=file MERGE_SHA="$adopt_head" EVENT_BEFORE="$adopt_base" repo adoption-bare adoptb_base="$(commit adoption-bare README.md "pre-ceremony tree")" adoptb_head="$(commit adoption-bare VERSION 0.1.0)" check "absent-at-base with a bare head still asks for the label" 0 "labeled=yes" \ facts_in adoption-bare VERSION_SOURCE=file MERGE_SHA="$adoptb_head" EVENT_BEFORE="$adoptb_base" GH_STUB=labeled-yes # --- the package-json backend ------------------------------------------------ repo pkg pkg_base="$(commit pkg package.json '{ "name": "fixture", "version": "1.1.9-dev" }')" pkg_head="$(commit pkg package.json '{ "name": "fixture", "version": "1.2.0" }')" check "package-json: head version via node" 0 "ver=1.2.0" \ facts_in pkg VERSION_SOURCE=package-json MERGE_SHA="$pkg_head" EVENT_BEFORE="$pkg_base" GH_STUB=labeled-yes check "package-json: base version via node" 0 "base_ver=1.1.9-dev" \ facts_in pkg VERSION_SOURCE=package-json MERGE_SHA="$pkg_head" EVENT_BEFORE="$pkg_base" GH_STUB=labeled-yes # --- refusals ---------------------------------------------------------------- check "missing VERSION_SOURCE refuses" 1 "VERSION_SOURCE is required" \ facts_in ceremony MERGE_SHA="$head_sha" EVENT_BEFORE="$base_sha" check "missing MERGE_SHA refuses" 1 "MERGE_SHA is required" \ facts_in ceremony VERSION_SOURCE=file EVENT_BEFORE="$base_sha" check "unknown backend refuses" 1 "unknown VERSION_SOURCE" \ facts_in ceremony VERSION_SOURCE=carrier-pigeon MERGE_SHA="$head_sha" EVENT_BEFORE="$base_sha" repo no-version nv_base="$(commit no-version README.md "a tree")" nv_head="$(commit no-version README.md "with no version at the head either")" check "no version at the head fails loudly" 1 "no such file" \ facts_in no-version VERSION_SOURCE=file MERGE_SHA="$nv_head" EVENT_BEFORE="$nv_base" summary