#!/usr/bin/env bash # Regression harness for detect-platform.sh's resolve_gitea_principal() — the # identity-first acting-principal resolution shared by the git wrappers # (mosaicstack/stack #1280). # # The contract under test (precedence: --login > MOSAIC_GIT_IDENTITY / # git config mosaic.gitIdentity > tea login list, which is the LAST resort): # 1. identity env + per-slot token present -> mode=identity, principal= # identity name, source names the identity's slot PATH (never a token # value). # 2. identity env + per-slot token ABSENT -> FAIL LOUD: nonzero, empty # stdout, stderr naming the identity and the expected slot path. # 3. identity env + --login -> --login wins (login mode resolves even when # the identity has no slot — operator intent beats environment). # 4. identity unset + no --login -> default mode: the tea login list # resolves the principal exactly as before (preserved behavior). # 5. no identity + no host-matching tea login -> default/host-credential # (preserved behavior; absence is not an error on the default path). # 6. identity on an UNRECOGNIZED host (no per-slot scheme) -> does not bind; # default mode (containment, mirroring get_gitea_token). # 7. --login with no host-bound token for that login -> FAIL LOUD, stderr # naming the login and the host. # 8. git config mosaic.gitIdentity is honored when the env var is unset. # 9. The resolver NEVER emits a token value — stdout/stderr of every # successful resolution must not contain the slot file's contents. # # Uses a stubbed tea binary, stubbed tea config.yml, stubbed credentials.json # and stubbed per-slot token files under a fake HOME. NEVER reads real secrets. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/gitea-principal-resolution}" FAKE_HOME="$WORK_DIR/home" REPO_DIR="$WORK_DIR/repo" BIN_DIR="$WORK_DIR/bin" CREDENTIALS_FILE="$FAKE_HOME/.config/mosaic/credentials.json" rm -rf "$WORK_DIR" mkdir -p "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens" "$FAKE_HOME/.config/tea" "$REPO_DIR" "$BIN_DIR" git -C "$REPO_DIR" init -q git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git cat > "$CREDENTIALS_FILE" <<'JSON' { "gitea": { "mosaicstack": { "url": "https://git.mosaicstack.dev", "token": "shared-mosaicstack-token" }, "usc": { "url": "https://git.uscllc.com", "token": "shared-usc-token" } } } JSON # tea's own config store: the source get_gitea_token_for_login reads. Logins # "alice" (mosaicstack) and "bob-usc" (usc) carry sentinel token values that # the assertions prove are NEVER emitted by the resolver. cat > "$FAKE_HOME/.config/tea/config.yml" <<'YAML' logins: - name: alice url: https://git.mosaicstack.dev token: SECRET-alice-tea-token - name: bob-usc url: https://git.uscllc.com token: SECRET-bob-usc-tea-token YAML # Stubbed tea: only what login resolution needs (`login list --output json`). cat > "$BIN_DIR/tea" <<'SH' #!/usr/bin/env bash set -euo pipefail if [[ "$*" == "login list --output json" ]]; then cat <<'JSON' [ {"name":"alice","url":"https://git.mosaicstack.dev","default":true}, {"name":"bob-usc","url":"https://git.uscllc.com"} ] JSON exit 0 fi exit 0 SH chmod +x "$BIN_DIR/tea" # Per-slot identity token with a sentinel value the assertions prove is never # emitted (proving "token came from the identity's slot BY PATH, not by value"). echo -n "SECRET-agentX-slot-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentX.token" fail=0 assert_eq() { local desc="$1" expected="$2" actual="$3" if [[ "$expected" != "$actual" ]]; then echo "FAIL: $desc — expected '$expected', got '$actual'" >&2 fail=1 fi } assert_contains() { local desc="$1" haystack="$2" needle="$3" if [[ "$haystack" != *"$needle"* ]]; then echo "FAIL: $desc — missing '$needle' in: $haystack" >&2 fail=1 fi } assert_not_contains() { local desc="$1" haystack="$2" needle="$3" if [[ "$haystack" == *"$needle"* ]]; then echo "FAIL: $desc — must not contain '$needle', got: $haystack" >&2 fail=1 fi } # Runs resolve_gitea_principal for $1=login_override $2=host inside REPO_DIR # (per-worktree git config resolves there) under a fake HOME, stubbed tea, and # stubbed credentials. Extra env (e.g. MOSAIC_GIT_IDENTITY) via $@. call_resolver() { local login="$1" host="$2"; shift 2 ( cd "$REPO_DIR" env -i HOME="$FAKE_HOME" PATH="$BIN_DIR:$PATH" \ GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null \ MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \ DETECT_PLATFORM_SH="$SCRIPT_DIR/detect-platform.sh" "$@" \ bash -c 'source "$DETECT_PLATFORM_SH"; resolve_gitea_principal "$1" "$2"' _ "$login" "$host" ) } field() { printf '%s' "$1" | cut -f"$2"; } # --------------------------------------------------------------------------- # 1. Identity env + slot present -> identity mode, slot named BY PATH, and no # token value ever emitted. # --------------------------------------------------------------------------- git -C "$REPO_DIR" config --unset mosaic.gitIdentity 2>/dev/null || true out=$(call_resolver "" "git.mosaicstack.dev" MOSAIC_GIT_IDENTITY=agentX) assert_eq "identity mode" "identity" "$(field "$out" 1)" assert_eq "identity principal" "agentX" "$(field "$out" 2)" assert_eq "identity slot source" \ "identity-slot:$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentX.token" \ "$(field "$out" 3)" assert_not_contains "identity stdout leaks token" "$out" "SECRET" # --------------------------------------------------------------------------- # 2. Identity env + slot ABSENT -> fail loud: nonzero, empty stdout, stderr # naming the identity and the expected slot path. # --------------------------------------------------------------------------- stderr_file="$WORK_DIR/stderr.tmp" set +e out=$(call_resolver "" "git.mosaicstack.dev" MOSAIC_GIT_IDENTITY=agentNoSlot 2>"$stderr_file") rc=$? set -e if [[ "$rc" -eq 0 ]]; then echo "FAIL: missing slot — expected nonzero return, got 0 (stdout='$out')" >&2 fail=1 fi if [[ -n "$out" ]]; then echo "FAIL: missing slot — expected empty stdout, got '$out'" >&2 fail=1 fi err=$(cat "$stderr_file") assert_contains "missing slot names identity" "$err" "agentNoSlot" assert_contains "missing slot names slot path" "$err" \ "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentNoSlot.token" assert_not_contains "missing-slot stderr leaks token" "$err" "SECRET" # --------------------------------------------------------------------------- # 3. Identity + --login -> --login wins. Also wins when the identity has NO # slot (no identity check may veto an explicit login). # --------------------------------------------------------------------------- out=$(call_resolver "alice" "git.mosaicstack.dev" MOSAIC_GIT_IDENTITY=agentX) assert_eq "login beats identity (mode)" "login" "$(field "$out" 1)" assert_eq "login beats identity (principal)" "alice" "$(field "$out" 2)" assert_eq "login source" "tea-login:alice" "$(field "$out" 3)" out=$(call_resolver "alice" "git.mosaicstack.dev" MOSAIC_GIT_IDENTITY=agentNoSlot) assert_eq "login beats slot-less identity" "login" "$(field "$out" 1)" # --------------------------------------------------------------------------- # 4. No identity, no --login -> default mode via the tea login list # (preserved behavior). # --------------------------------------------------------------------------- out=$(call_resolver "" "git.mosaicstack.dev") assert_eq "default mode" "default" "$(field "$out" 1)" assert_eq "default principal" "alice" "$(field "$out" 2)" assert_eq "default source" "tea-default" "$(field "$out" 3)" # --------------------------------------------------------------------------- # 5. No identity, no --login, no host-matching tea login -> default with the # host credential (absence is not an error on the default path). # --------------------------------------------------------------------------- out=$(call_resolver "" "git.unknown.test") assert_eq "no-match default mode" "default" "$(field "$out" 1)" assert_eq "no-match default principal" "" "$(field "$out" 2)" assert_eq "no-match default source" "host-credential" "$(field "$out" 3)" # --------------------------------------------------------------------------- # 6. Identity on an UNRECOGNIZED host -> does not bind; default mode # (containment, mirroring get_gitea_token's scope). # --------------------------------------------------------------------------- out=$(call_resolver "" "github.com" MOSAIC_GIT_IDENTITY=agentX) assert_eq "unrecognized host falls to default" "default" "$(field "$out" 1)" # --------------------------------------------------------------------------- # 7. --login with no host-bound token for that login -> fail loud, stderr # naming the login and the host. # --------------------------------------------------------------------------- : > "$stderr_file" set +e out=$(call_resolver "ghost-login" "git.mosaicstack.dev" 2>"$stderr_file") rc=$? set -e if [[ "$rc" -eq 0 ]]; then echo "FAIL: unknown --login — expected nonzero return, got 0 (stdout='$out')" >&2 fail=1 fi err=$(cat "$stderr_file") assert_contains "unknown login names login" "$err" "ghost-login" assert_contains "unknown login names host" "$err" "git.mosaicstack.dev" # A cross-host login (exists, but for usc) must ALSO fail loud for mosaicstack. set +e out=$(call_resolver "bob-usc" "git.mosaicstack.dev" 2>"$stderr_file") rc=$? set -e if [[ "$rc" -eq 0 ]]; then echo "FAIL: cross-host --login — expected nonzero return, got 0" >&2 fail=1 fi # --------------------------------------------------------------------------- # 8. git config mosaic.gitIdentity honored when env is unset. # --------------------------------------------------------------------------- git -C "$REPO_DIR" config mosaic.gitIdentity agentX out=$(call_resolver "" "git.mosaicstack.dev") assert_eq "git-config identity mode" "identity" "$(field "$out" 1)" assert_eq "git-config identity principal" "agentX" "$(field "$out" 2)" git -C "$REPO_DIR" config --unset mosaic.gitIdentity # --------------------------------------------------------------------------- # 9. Cross-host slot layout: the usc slot path is chosen for the usc host. # --------------------------------------------------------------------------- echo -n "SECRET-agentX-usc-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-usc-agentX.token" out=$(call_resolver "" "git.uscllc.com" MOSAIC_GIT_IDENTITY=agentX) assert_eq "usc identity mode" "identity" "$(field "$out" 1)" assert_eq "usc slot source" \ "identity-slot:$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-usc-agentX.token" \ "$(field "$out" 3)" if [[ "$fail" -eq 0 ]]; then echo "resolve_gitea_principal identity-first resolution regression passed" fi exit "$fail"