diff --git a/tests/test-sync-marketplace-mirror.sh b/tests/test-sync-marketplace-mirror.sh new file mode 100755 index 0000000..7fd6de5 --- /dev/null +++ b/tests/test-sync-marketplace-mirror.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SCRIPT="$REPO_ROOT/scripts/sync-marketplace-mirror.sh" +PASS=0 +FAIL=0 + +pass() { echo " PASS: $1"; PASS=$((PASS + 1)); } +fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); } + +# Minimal fixture: a bare directory with no .git of its own. Since +# scripts/sync-marketplace-mirror.sh resolves REPO_ROOT via +# `git rev-parse --show-toplevel 2>/dev/null || pwd`, and mktemp -d creates +# directories outside any git worktree, cd'ing into the fixture before +# invoking the script makes REPO_ROOT resolve to the fixture itself -- so +# every test runs against an isolated .claude-plugin/ + .github/plugin/ pair +# instead of this repo's real marketplace.json files. +make_fixture() { + mktemp -d +} + +SRC_REL=".claude-plugin/marketplace.json" +DST_REL=".github/plugin/marketplace.json" +CONTENT_A='{"plugins":[{"name":"demo","version":"1.0.0"}]}' +CONTENT_B='{"plugins":[{"name":"demo","version":"2.0.0"}]}' + +write_src() { + local dir="$1" content="$2" + mkdir -p "$dir/.claude-plugin" + printf '%s' "$content" > "$dir/$SRC_REL" +} + +write_dst() { + local dir="$1" content="$2" + mkdir -p "$dir/.github/plugin" + printf '%s' "$content" > "$dir/$DST_REL" +} + +run_script() { + local dir="$1" + shift + (cd "$dir" && bash "$SCRIPT" "$@") +} + +CLEANUP_DIRS=() +trap 'rm -rf "${CLEANUP_DIRS[@]}"' EXIT +track() { CLEANUP_DIRS+=("$1"); } + +# --- 1. No .claude-plugin/marketplace.json at all: real-sync mode is a no-op, exit 0 --- +echo "" +echo "--- missing source: real sync exits 0 and creates nothing ---" +FIXTURE1="$(make_fixture)"; track "$FIXTURE1" +if run_script "$FIXTURE1" > /dev/null 2>&1 && [[ ! -e "$FIXTURE1/$DST_REL" ]]; then + pass "missing source exits 0 in real-sync mode without creating a mirror" +else + fail "missing source should exit 0 and create nothing in real-sync mode" +fi + +# --- 2. No source: --check mode is also a no-op, exit 0 (nothing to drift-check) --- +echo "" +echo "--- missing source: --check exits 0 (nothing to compare) ---" +FIXTURE2="$(make_fixture)"; track "$FIXTURE2" +if run_script "$FIXTURE2" --check > /dev/null 2>&1; then + pass "missing source exits 0 in --check mode" +else + fail "missing source should exit 0 in --check mode, not report drift" +fi + +# --- 3. Source exists, mirror missing entirely: --check reports drift (exit 1) --- +echo "" +echo "--- --check reports drift when the mirror file does not exist yet ---" +FIXTURE3="$(make_fixture)"; track "$FIXTURE3" +write_src "$FIXTURE3" "$CONTENT_A" +if run_script "$FIXTURE3" --check > /dev/null 2>&1; then + fail "exited 0 with no mirror file present -- expected drift (exit 1)" +else + pass "missing mirror file is reported as drift" +fi + +# --- 4. Real sync creates the mirror (and its parent dir) byte-identical to the source --- +echo "" +echo "--- real sync creates .github/plugin/marketplace.json byte-identical to source ---" +FIXTURE4="$(make_fixture)"; track "$FIXTURE4" +write_src "$FIXTURE4" "$CONTENT_A" +if run_script "$FIXTURE4" > /dev/null 2>&1 \ + && [[ -f "$FIXTURE4/$DST_REL" ]] \ + && diff -q "$FIXTURE4/$SRC_REL" "$FIXTURE4/$DST_REL" > /dev/null 2>&1; then + pass "real sync creates the mirror file identical to the source" +else + fail "real sync did not create a byte-identical mirror file" +fi + +# --- 5. --check is clean immediately after a real sync --- +echo "" +echo "--- --check is clean right after syncing ---" +if run_script "$FIXTURE4" --check > /dev/null 2>&1; then + pass "no drift reported immediately after syncing" +else + fail "drift reported right after syncing -- sync and check disagree" +fi + +# --- 6. Editing the source after a sync introduces drift; re-sync clears it --- +echo "" +echo "--- source edited after sync is detected as drift, cleared by re-sync ---" +write_src "$FIXTURE4" "$CONTENT_B" +if run_script "$FIXTURE4" --check > /dev/null 2>&1; then + fail "no drift reported after editing the source -- expected drift" +else + pass "editing the source after a sync is detected as drift" + run_script "$FIXTURE4" > /dev/null 2>&1 + if run_script "$FIXTURE4" --check > /dev/null 2>&1; then + pass "re-sync clears the drift" + else + fail "re-sync did not clear the drift" + fi +fi + +# --- 7. A mirror that was hand-seeded already identical to the source: --check is clean --- +echo "" +echo "--- --check is clean when the mirror already matches, without ever syncing ---" +FIXTURE7="$(make_fixture)"; track "$FIXTURE7" +write_src "$FIXTURE7" "$CONTENT_A" +write_dst "$FIXTURE7" "$CONTENT_A" +if run_script "$FIXTURE7" --check > /dev/null 2>&1; then + pass "an already-in-sync mirror reports no drift" +else + fail "an already-in-sync mirror should report no drift" +fi + +# --- 8. A stale, pre-existing mirror that differs from source: --check flags it, sync fixes it --- +echo "" +echo "--- a stale pre-existing mirror is flagged by --check and overwritten by sync ---" +FIXTURE8="$(make_fixture)"; track "$FIXTURE8" +write_src "$FIXTURE8" "$CONTENT_A" +write_dst "$FIXTURE8" "$CONTENT_B" +if run_script "$FIXTURE8" --check > /dev/null 2>&1; then + fail "a stale mirror was not flagged as drift" +else + pass "a stale pre-existing mirror is flagged as drift" +fi +run_script "$FIXTURE8" > /dev/null 2>&1 +if diff -q "$FIXTURE8/$SRC_REL" "$FIXTURE8/$DST_REL" > /dev/null 2>&1; then + pass "real sync overwrites the stale mirror to match the source" +else + fail "real sync did not overwrite the stale mirror" +fi + +# --- 9. An unknown/extra positional argument is rejected with a usage error --- +echo "" +echo "--- an unrecognized argument is rejected (usage, exit 1) ---" +FIXTURE9="$(make_fixture)"; track "$FIXTURE9" +write_src "$FIXTURE9" "$CONTENT_A" +if run_script "$FIXTURE9" --bogus > /dev/null 2>&1; then + fail "exited 0 with an unrecognized argument -- expected a usage error" +else + pass "an unrecognized argument is rejected" +fi + +# --- 10. --check followed by a stray extra argument is also rejected --- +echo "" +echo "--- --check plus a trailing extra argument is rejected (usage, exit 1) ---" +if run_script "$FIXTURE9" --check extra > /dev/null 2>&1; then + fail "exited 0 with --check plus a trailing argument -- expected a usage error" +else + pass "--check plus a trailing extra argument is rejected" +fi + +# --- 11. Real sync is idempotent: running it twice in a row leaves the mirror unchanged --- +echo "" +echo "--- running real sync twice in a row is idempotent ---" +FIXTURE11="$(make_fixture)"; track "$FIXTURE11" +write_src "$FIXTURE11" "$CONTENT_A" +run_script "$FIXTURE11" > /dev/null 2>&1 +run_script "$FIXTURE11" > /dev/null 2>&1 +if run_script "$FIXTURE11" --check > /dev/null 2>&1; then + pass "running sync twice in a row is idempotent" +else + fail "a second sync run introduced unexpected drift" +fi + +echo "" +echo "Results: $PASS passed, $FAIL failed" +[[ $FAIL -eq 0 ]]