All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
core.filemode=false in this worktree meant the initial commit recorded git-credential-mosaic, test-git-credential-mosaic.sh, and test-gitea-token-identity.sh as 100644. git invokes a path-configured credential.helper directly (exec, not `sh <path>`), so git-credential-mosaic must carry the executable bit; the two test scripts match the 100755 convention already used by the other test-*.sh harnesses in this directory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
162 lines
8.1 KiB
Bash
Executable File
162 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Regression harness for `git-credential-mosaic` — per-agent Gitea identity
|
|
# resolution (Gate-16 author≠reviewer separation).
|
|
#
|
|
# Covers:
|
|
# 1. Identity resolution priority: MOSAIC_GIT_IDENTITY env > git config
|
|
# mosaic.gitIdentity (per-worktree) > git-supplied username.
|
|
# 2. Correct per-slot token file path chosen per host
|
|
# (gitea-usc-<id>.token vs gitea-mosaicstack-<id>.token).
|
|
# 3. Per-slot token present -> emits that identity + token.
|
|
# 4. Per-slot token absent -> falls back to the shared account
|
|
# (backward-compat / no-op for hosts without per-slot tokens).
|
|
# 5. Unknown/unrelated host -> exits 0 with no output (passthrough).
|
|
#
|
|
# Uses stubbed token files under a fake HOME + a real (throwaway) git repo.
|
|
# 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/git-credential-mosaic}"
|
|
FAKE_HOME="$WORK_DIR/home"
|
|
REPO_DIR="$WORK_DIR/repo"
|
|
# Mirror the real deployed layout (~/.config/mosaic/tools/{git,_lib}/) under the
|
|
# fake HOME: git-credential-mosaic resolves its credentials.sh sibling via a
|
|
# script-relative path (BASH_SOURCE), so the copy must live next to a stubbed
|
|
# _lib/credentials.sh, not the real one, to keep this test hermetic.
|
|
HELPER="$FAKE_HOME/.config/mosaic/tools/git/git-credential-mosaic"
|
|
|
|
rm -rf "$WORK_DIR"
|
|
mkdir -p "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens" \
|
|
"$FAKE_HOME/.config/mosaic/tools/git" \
|
|
"$FAKE_HOME/.config/mosaic/tools/_lib" \
|
|
"$REPO_DIR"
|
|
|
|
cp "$SCRIPT_DIR/git-credential-mosaic" "$HELPER"
|
|
chmod +x "$HELPER"
|
|
|
|
git -C "$REPO_DIR" init -q
|
|
git -C "$REPO_DIR" config user.email "test@example.invalid"
|
|
git -C "$REPO_DIR" config user.name "Test"
|
|
|
|
# Fake shared-account credential loader — stands in for
|
|
# tools/_lib/credentials.sh's load_credentials(), scoped to this test only.
|
|
cat > "$FAKE_HOME/.config/mosaic/tools/_lib/credentials.sh" <<'SH'
|
|
load_credentials() {
|
|
case "$1" in
|
|
gitea-mosaicstack) GITEA_URL="https://git.mosaicstack.dev"; GITEA_TOKEN="shared-mosaicstack-token"; export GITEA_URL GITEA_TOKEN; return 0 ;;
|
|
gitea-usc) GITEA_URL="https://git.uscllc.com"; GITEA_TOKEN="shared-usc-token"; export GITEA_URL GITEA_TOKEN; return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
SH
|
|
|
|
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
|
|
}
|
|
|
|
# Feed "host=<h>\nusername=<u>\n\n" on stdin (mirrors git's credential protocol)
|
|
# and run the helper with the fake HOME, inside REPO_DIR (so `git config
|
|
# mosaic.gitIdentity` resolves per-worktree), plus any extra env passed in $@.
|
|
run_helper() {
|
|
local host="$1" username_in="$2"; shift 2
|
|
(
|
|
cd "$REPO_DIR"
|
|
env -i HOME="$FAKE_HOME" PATH="$PATH" "$@" bash "$HELPER" get <<EOF
|
|
host=$host
|
|
username=$username_in
|
|
|
|
EOF
|
|
)
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. No identity resolvable anywhere, no per-slot token -> shared fallback
|
|
# (backward-compat: unchanged behavior when nothing is configured).
|
|
# ---------------------------------------------------------------------------
|
|
git -C "$REPO_DIR" config --unset mosaic.gitIdentity 2>/dev/null || true
|
|
out=$(run_helper "git.mosaicstack.dev" "")
|
|
assert_eq "shared fallback: username" "username=git" "$(echo "$out" | grep '^username=')"
|
|
assert_eq "shared fallback: password" "password=shared-mosaicstack-token" "$(echo "$out" | grep '^password=')"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. git-supplied username resolves to an identity WITH a per-slot token ->
|
|
# that identity + token wins over the shared account.
|
|
# ---------------------------------------------------------------------------
|
|
echo -n "agentA-mosaicstack-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentA.token"
|
|
out=$(run_helper "git.mosaicstack.dev" "agentA")
|
|
assert_eq "username-resolved identity: username" "username=agentA" "$(echo "$out" | grep '^username=')"
|
|
assert_eq "username-resolved identity: password" "password=agentA-mosaicstack-token" "$(echo "$out" | grep '^password=')"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. git config mosaic.gitIdentity (per-worktree) beats git-supplied username.
|
|
# ---------------------------------------------------------------------------
|
|
echo -n "agentB-mosaicstack-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentB.token"
|
|
git -C "$REPO_DIR" config mosaic.gitIdentity agentB
|
|
out=$(run_helper "git.mosaicstack.dev" "agentA")
|
|
assert_eq "git-config beats username: username" "username=agentB" "$(echo "$out" | grep '^username=')"
|
|
assert_eq "git-config beats username: password" "password=agentB-mosaicstack-token" "$(echo "$out" | grep '^password=')"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. MOSAIC_GIT_IDENTITY env beats git config mosaic.gitIdentity.
|
|
# ---------------------------------------------------------------------------
|
|
echo -n "agentC-mosaicstack-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentC.token"
|
|
out=$(run_helper "git.mosaicstack.dev" "agentA" MOSAIC_GIT_IDENTITY=agentC)
|
|
assert_eq "env beats git-config: username" "username=agentC" "$(echo "$out" | grep '^username=')"
|
|
assert_eq "env beats git-config: password" "password=agentC-mosaicstack-token" "$(echo "$out" | grep '^password=')"
|
|
git -C "$REPO_DIR" config --unset mosaic.gitIdentity
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5. Identity resolves, but no matching per-slot token file -> falls back to
|
|
# the shared account (per-agent identity is opt-in, not a hard requirement).
|
|
# ---------------------------------------------------------------------------
|
|
out=$(run_helper "git.mosaicstack.dev" "no-such-agent")
|
|
assert_eq "no per-slot token: username" "username=git" "$(echo "$out" | grep '^username=')"
|
|
assert_eq "no per-slot token: password" "password=shared-mosaicstack-token" "$(echo "$out" | grep '^password=')"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 6. Correct per-slot token PATH is chosen per host: same agent id, different
|
|
# host prefix (gitea-usc- vs gitea-mosaicstack-).
|
|
# ---------------------------------------------------------------------------
|
|
echo -n "agentD-usc-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-usc-agentD.token"
|
|
out=$(run_helper "git.uscllc.com" "agentD")
|
|
assert_eq "host-scoped token path (usc): username" "username=agentD" "$(echo "$out" | grep '^username=')"
|
|
assert_eq "host-scoped token path (usc): password" "password=agentD-usc-token" "$(echo "$out" | grep '^password=')"
|
|
# agentD has NO mosaicstack token -> must fall back to shared mosaicstack, not
|
|
# leak the usc token across hosts.
|
|
out=$(run_helper "git.mosaicstack.dev" "agentD")
|
|
assert_eq "host-scoped token path (cross-host must not leak): username" "username=git" "$(echo "$out" | grep '^username=')"
|
|
assert_eq "host-scoped token path (cross-host must not leak): password" "password=shared-mosaicstack-token" "$(echo "$out" | grep '^password=')"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 7. Unrelated/unknown host -> exit 0, no output (passthrough for non-Gitea
|
|
# remotes, e.g. github.com via a different credential helper).
|
|
# ---------------------------------------------------------------------------
|
|
out=$(run_helper "github.com" "agentA")
|
|
assert_eq "unknown host: no output" "" "$out"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 8. Non-"get" verb (store/erase) -> exit 0, no output (git-credential
|
|
# protocol: this helper only implements get).
|
|
# ---------------------------------------------------------------------------
|
|
store_out=$(cd "$REPO_DIR" && env -i HOME="$FAKE_HOME" PATH="$PATH" bash "$HELPER" store <<EOF
|
|
host=git.mosaicstack.dev
|
|
username=agentA
|
|
password=whatever
|
|
|
|
EOF
|
|
)
|
|
assert_eq "store verb: no output" "" "$store_out"
|
|
|
|
if [[ "$fail" -eq 0 ]]; then
|
|
echo "git-credential-mosaic identity resolution regression passed"
|
|
fi
|
|
|
|
exit "$fail"
|