#!/usr/bin/env bash
# Fixture tests for tools/site-deploy.sh using a local bare remote and a fake
# SSH transport. Covers missing-branch initial publish, identical-tree no-op,
# changed-tree publish, and ls-remote transport failure classification.
set -euo pipefail
cd "$(dirname "$0")/.." || exit 1
fail=0
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
setup_fixture_repo() {
local root=$1
mkdir -p "$root/tools" "$root/site/dist"
cp tools/site-deploy.sh "$root/tools/site-deploy.sh"
cat > "$root/tools/site-build.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DIST="$ROOT/site/dist"
VARIANT="${SITE_DEPLOY_FIXTURE_VARIANT:-base}"
rm -rf "$DIST"
mkdir -p "$DIST"
printf '%s\n' "$VARIANT" > "$DIST/content.txt"
printf '
%s\n' \
"$VARIANT" "$SITE_SOURCE_REVISION" > "$DIST/index.html"
printf '' > "$DIST/.nojekyll"
EOF
chmod +x "$root/tools/site-build.sh"
cat > "$root/fake-ssh.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
REMOTE="$ROOT/remote.git"
LOG="$ROOT/ssh.log"
SERVICES="$ROOT/ssh-services.log"
joined="$*"
printf '%s\n' "$joined" >> "$LOG"
case "$joined" in
*git-upload-pack*)
service="git-upload-pack"
;;
*git-receive-pack*)
service="git-receive-pack"
;;
*)
echo "fake-ssh: unsupported invocation: $joined" >&2
exit 97
;;
esac
printf '%s\n' "$service" >> "$SERVICES"
if [ "${FAKE_SSH_FAIL_ON:-}" = "$service" ]; then
echo "fake-ssh: forced failure for $service" >&2
exit 66
fi
case "$service" in
git-upload-pack)
exec git-upload-pack "$REMOTE"
;;
git-receive-pack)
exec git-receive-pack "$REMOTE"
;;
esac
EOF
chmod +x "$root/fake-ssh.sh"
git init --bare -q "$root/remote.git"
git -C "$root" init -q
git -C "$root" config user.name "Fixture User"
git -C "$root" config user.email "fixture@example.com"
git -C "$root" config core.sshCommand "$root/fake-ssh.sh"
git -C "$root" remote add origin "ssh://fixture/placeholder.git"
cat > "$root/.gitignore" <<'EOF'
remote.git/
site/dist/
ssh.log
ssh-services.log
EOF
printf 'fixture source\n' > "$root/source.txt"
git -C "$root" add .gitignore source.txt fake-ssh.sh tools/site-build.sh tools/site-deploy.sh
git -C "$root" commit -q -m "fixture source"
}
run_deploy_ok() {
local root=$1
local variant=$2
(
cd "$root"
GIT_SSH_VARIANT=simple SITE_DEPLOY_FIXTURE_VARIANT="$variant" bash tools/site-deploy.sh
) 2>&1
}
run_deploy_fail() {
local root=$1
local variant=$2
local fail_on=$3
(
cd "$root"
GIT_SSH_VARIANT=simple SITE_DEPLOY_FIXTURE_VARIANT="$variant" FAKE_SSH_FAIL_ON="$fail_on" \
bash tools/site-deploy.sh
) 2>&1
}
remote_head() {
git -C "$1/remote.git" rev-parse "refs/heads/$2"
}
remote_tree() {
git -C "$1/remote.git" rev-parse "refs/heads/$2^{tree}"
}
parent_count() {
git -C "$1/remote.git" rev-list --parents -n 1 "refs/heads/$2" | awk '{ print NF - 1 }'
}
count_service() {
local root=$1
local service=$2
if [ ! -f "$root/ssh-services.log" ]; then
echo 0
return
fi
awk -v service="$service" '$0 == service { count++ } END { print count + 0 }' "$root/ssh-services.log"
}
reset_ssh_logs() {
: > "$1/ssh.log"
: > "$1/ssh-services.log"
}
echo "=== fixture: missing branch initial publish ==="
root="$tmp/missing-branch"
setup_fixture_repo "$root"
output="$(run_deploy_ok "$root" base)" || {
echo "FAIL fixture missing-branch: expected success"
echo "$output"
fail=1
}
echo "$output" | grep -q "missing remote pages branch origin/pages, initial publish" || {
echo "FAIL fixture missing-branch: did not report initial publish"
echo "$output"
fail=1
}
head_before="$(remote_head "$root" pages)"
tree_before="$(remote_tree "$root" pages)"
[ "$(parent_count "$root" pages)" = "0" ] || {
echo "FAIL fixture missing-branch: initial publish commit is not orphaned"
fail=1
}
[ "$(count_service "$root" git-upload-pack)" = "1" ] || {
echo "FAIL fixture missing-branch: ls-remote did not use fake SSH exactly once"
fail=1
}
[ "$(count_service "$root" git-receive-pack)" = "1" ] || {
echo "FAIL fixture missing-branch: push did not use fake SSH exactly once"
fail=1
}
echo "=== fixture: identical-tree no-op ==="
reset_ssh_logs "$root"
output="$(run_deploy_ok "$root" base)" || {
echo "FAIL fixture identical-tree: expected success"
echo "$output"
fail=1
}
echo "$output" | grep -q "public-site content is current on origin/pages" || {
echo "FAIL fixture identical-tree: missing current-content message"
echo "$output"
fail=1
}
echo "$output" | grep -q "nothing to publish" || {
echo "FAIL fixture identical-tree: missing no-op message"
echo "$output"
fail=1
}
[ "$head_before" = "$(remote_head "$root" pages)" ] || {
echo "FAIL fixture identical-tree: remote head changed on no-op"
fail=1
}
[ "$(count_service "$root" git-upload-pack)" = "2" ] || {
echo "FAIL fixture identical-tree: expected ls-remote + fetch through fake SSH"
fail=1
}
[ "$(count_service "$root" git-receive-pack)" = "0" ] || {
echo "FAIL fixture identical-tree: no-op should not push"
fail=1
}
echo "=== fixture: changed-tree publish ==="
reset_ssh_logs "$root"
output="$(run_deploy_ok "$root" changed)" || {
echo "FAIL fixture changed-tree: expected success"
echo "$output"
fail=1
}
head_after="$(remote_head "$root" pages)"
tree_after="$(remote_tree "$root" pages)"
[ "$head_before" != "$head_after" ] || {
echo "FAIL fixture changed-tree: remote head did not change"
fail=1
}
[ "$tree_before" != "$tree_after" ] || {
echo "FAIL fixture changed-tree: remote tree did not change"
fail=1
}
[ "$(parent_count "$root" pages)" = "0" ] || {
echo "FAIL fixture changed-tree: publish commit is not orphaned"
fail=1
}
[ "$(count_service "$root" git-upload-pack)" = "2" ] || {
echo "FAIL fixture changed-tree: expected ls-remote + fetch through fake SSH"
fail=1
}
[ "$(count_service "$root" git-receive-pack)" = "1" ] || {
echo "FAIL fixture changed-tree: changed tree did not push exactly once"
fail=1
}
echo "=== fixture: transport failure is not treated as missing branch ==="
root="$tmp/transport-failure"
setup_fixture_repo "$root"
if output="$(run_deploy_fail "$root" base git-upload-pack)"; then
echo "FAIL fixture transport-failure: expected non-zero exit"
fail=1
else
echo "$output" | grep -q "fake-ssh: forced failure for git-upload-pack" || {
echo "FAIL fixture transport-failure: missing forced-failure output"
echo "$output"
fail=1
}
if echo "$output" | grep -q "missing remote pages branch"; then
echo "FAIL fixture transport-failure: transport failure was misclassified as missing branch"
echo "$output"
fail=1
fi
fi
echo "=== fixture: dirty source tree is not published ==="
root="$tmp/dirty-source"
setup_fixture_repo "$root"
printf 'uncommitted\n' >> "$root/source.txt"
if output="$(run_deploy_ok "$root" base)"; then
echo "FAIL fixture dirty-source: expected non-zero exit"
fail=1
else
echo "$output" | grep -q "refusing to publish a dirty source tree" || {
echo "FAIL fixture dirty-source: missing dirty-tree refusal"
echo "$output"
fail=1
}
[ ! -e "$root/remote.git/refs/heads/pages" ] || {
echo "FAIL fixture dirty-source: pages branch was created"
fail=1
}
fi
if [ "$fail" -ne 0 ]; then
echo "site deploy fixtures: FAILED"
exit 1
fi
echo "site deploy fixtures: OK"