#!/usr/bin/env bash # Regression harness for detect-platform.sh's get_gitea_token() per-agent # identity resolution (Gate-16 author≠reviewer separation) — the API-tooling # counterpart to git-credential-mosaic, so pr-create.sh/issue-create.sh/etc. # open records under the resolved agent identity, not the shared account. # # Covers: # 1. Identity resolution priority: MOSAIC_GIT_IDENTITY env > git config # mosaic.gitIdentity (per-worktree). # 2. Correct per-slot token file path chosen per host # (gitea-usc-.token vs gitea-mosaicstack-.token). # 3. Per-slot token present -> that token is returned (agent-authored calls). # 4. Per-slot token absent -> falls back to the shared credential-loader # token (backward-compat / no-op for hosts without per-slot tokens). # 5. Unrelated host with no shared credentials configured -> failure # (unchanged, existing behavior). # # Uses a stubbed credentials.json + stubbed per-slot token files under a fake # HOME. NEVER reads real secrets or touches the real ~/.config/mosaic/secrets. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/gitea-token-identity}" FAKE_HOME="$WORK_DIR/home" REPO_DIR="$WORK_DIR/repo" CREDENTIALS_FILE="$FAKE_HOME/.config/mosaic/credentials.json" rm -rf "$WORK_DIR" mkdir -p "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens" "$REPO_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 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 } # Runs get_gitea_token for $1=host inside REPO_DIR (per-worktree git config # resolves there) with a fake HOME + the stub credentials.json, plus any # extra env passed in $@. call_get_gitea_token() { local host="$1"; shift ( cd "$REPO_DIR" # shellcheck disable=SC2016 # deliberately deferred: $DETECT_PLATFORM_SH is # expanded by the INNER bash -c (via the exported env var below), not here. env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \ DETECT_PLATFORM_SH="$SCRIPT_DIR/detect-platform.sh" "$@" \ bash -c 'source "$DETECT_PLATFORM_SH"; get_gitea_token "$1"' _ "$host" ) } # --------------------------------------------------------------------------- # 1. No identity resolvable -> shared credential-loader token (unchanged). # --------------------------------------------------------------------------- git -C "$REPO_DIR" config --unset mosaic.gitIdentity 2>/dev/null || true out=$(call_get_gitea_token "git.mosaicstack.dev") assert_eq "shared fallback (no identity)" "shared-mosaicstack-token" "$out" # --------------------------------------------------------------------------- # 2. git config mosaic.gitIdentity resolves to an agent WITH a per-slot # token -> that token wins over the shared account. # --------------------------------------------------------------------------- echo -n "agentA-mosaicstack-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentA.token" git -C "$REPO_DIR" config mosaic.gitIdentity agentA out=$(call_get_gitea_token "git.mosaicstack.dev") assert_eq "git-config identity token" "agentA-mosaicstack-token" "$out" # --------------------------------------------------------------------------- # 3. MOSAIC_GIT_IDENTITY env beats git config mosaic.gitIdentity. # --------------------------------------------------------------------------- echo -n "agentB-mosaicstack-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentB.token" out=$(call_get_gitea_token "git.mosaicstack.dev" MOSAIC_GIT_IDENTITY=agentB) assert_eq "env beats git-config identity token" "agentB-mosaicstack-token" "$out" # --------------------------------------------------------------------------- # 4. Identity resolves but has no per-slot token for THIS host -> falls back # to the shared token (per-agent identity is opt-in per host). # --------------------------------------------------------------------------- git -C "$REPO_DIR" config mosaic.gitIdentity no-such-agent out=$(call_get_gitea_token "git.mosaicstack.dev") assert_eq "no per-slot token falls back to shared" "shared-mosaicstack-token" "$out" # --------------------------------------------------------------------------- # 5. Correct per-slot token PATH per host: same agent id, only a usc token # exists -> usc host returns it, mosaicstack host must NOT leak it and # instead falls back to the shared mosaicstack token. # --------------------------------------------------------------------------- echo -n "agentD-usc-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-usc-agentD.token" git -C "$REPO_DIR" config mosaic.gitIdentity agentD out=$(call_get_gitea_token "git.uscllc.com") assert_eq "host-scoped token path (usc)" "agentD-usc-token" "$out" out=$(call_get_gitea_token "git.mosaicstack.dev") assert_eq "host-scoped token path (no cross-host leak)" "shared-mosaicstack-token" "$out" git -C "$REPO_DIR" config --unset mosaic.gitIdentity if [[ "$fail" -eq 0 ]]; then echo "get_gitea_token identity resolution regression passed" fi exit "$fail"