#!/usr/bin/env bash
# tests/cloud-snapshot/run-tests.sh -- offline fakebin harness for
# scripts/cloud-snapshot.sh + scripts/snapshot_scrub.py (DOCFIX-102).
# Fake juju replays fixtures keyed by argv patterns (real jq/python3/sha256sum
# stay real); CLOUD_SNAPSHOT_ROOT and a scratch HOME confine every write to
# $TMP. Covers: happy capture (manifest + verified checksums, benign
# secret-backend NOT redacted), juju-unreachable HELD (nothing written),
# partial-capture counted FAIL, --with-controller-backup (archive lands under
# HOME, never in the snapshot dir, recorded never-commit), backup-failure
# counted, and the secret-scrub case (planted credential-shaped value caught,
# absent from every written byte, WARN verdict).
# Mutates nothing outside $TMP. Exit: 0 all pass | 1 any failed.  ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/../.." && pwd)"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
BIN="$TMP/bin"; mkdir -p "$BIN"

cat > "$BIN/juju" <<'FB'
#!/usr/bin/env bash
# fake juju: replay fixtures from $FIXDIR keyed by argv patterns.
# Truthful mocks: unreachable = stderr + rc1 (real juju shape), not empty
# stdout; export-bundle failure likewise. Fail loud on unmatched argv.
A="$*"
case "$A" in
  version) echo "3.6.25-genericlinux-amd64" ;;
  *"status -m"*"--format=json"*)
    if [ -f "$FIXDIR/unreach" ]; then
      echo "ERROR connection is shut down" >&2; exit 1
    fi
    cat "$FIXDIR/status.json" ;;
  *"export-bundle -m"*)
    if [ -f "$FIXDIR/export-fail" ]; then
      echo "ERROR cannot export bundle: connection is shut down" >&2; exit 1
    fi
    cat "$FIXDIR/bundle.yaml" ;;
  *"model-config -m"*) cat "$FIXDIR/model-config.yaml" ;;
  *"create-backup -m admin/controller"*)
    if [ -f "$FIXDIR/backup-fail" ]; then
      echo "ERROR backup failed: mongodump exited 1" >&2; exit 1
    fi
    # find --filename <path> in the argv (real create-backup downloads there)
    FN=""
    while [ $# -gt 0 ]; do
      [ "$1" = "--filename" ] && { FN="$2"; break; }
      shift
    done
    [ -n "$FN" ] || { echo "fake-juju: create-backup without --filename" >&2; exit 1; }
    printf 'fake-controller-backup-payload %s\n' "$(date -u +%s)" > "$FN"
    echo "Downloaded to $FN"
    ;;
  *) echo "fake-juju: unmatched: $A" >&2; exit 1 ;;
esac
FB
chmod +x "$BIN/juju"

mkgood() { # healthy fixture set
  local d="$1"; mkdir -p "$d"
  cat > "$d/status.json" <<'J'
{"model":{"name":"openstack","controller":"testcloud"},
 "applications":{
  "keystone":{"units":{"keystone/0":{"workload-status":{"current":"active","message":"PO: Unit is ready"}}}},
  "nova-compute":{"units":{"nova-compute/0":{"workload-status":{"current":"active"}}}}
}}
J
  cat > "$d/bundle.yaml" <<'Y'
applications:
  keystone:
    charm: keystone
    channel: 2024.1/stable
    num_units: 1
    options:
      region: RegionOne
  vault:
    charm: vault
    channel: 1.8/stable
    num_units: 1
Y
  # truthful mock: real juju model-config yaml carries secret-backend (a
  # credential-SHAPED key with a benign value -- the false-positive guard)
  cat > "$d/model-config.yaml" <<'Y'
default-base: ubuntu@22.04
logging-config: <root>=INFO
secret-backend: auto
transmit-vendor-metrics: false
Y
}

run() { # run <want_rc> <regex> <label> <fixdir> <outroot> [extra args...]
  local want="$1" rx="$2" label="$3" fd="$4" root="$5"; shift 5
  local out rc
  mkdir -p "$root" "$root/home"    # per-case HOME: no cross-case artifact bleed
  out="$(env FIXDIR="$fd" PATH="$BIN:$PATH" HOME="$root/home" \
        CLOUD_SNAPSHOT_ROOT="$root" \
        bash "$REPO/scripts/cloud-snapshot.sh" "$@" 2>&1)"; rc=$?
  if [[ "$rc" == "$want" ]] && grep -qE "$rx" <<<"$out"; then
    echo "  PASS  $label"; PASS=$((PASS+1))
  else
    echo "  FAIL  $label (rc=$rc want=$want)"
    grep -E '\[(FAIL|WARN)\]|CLOUD-SNAPSHOT' <<<"$out" | head -6 | sed 's/^/        /'
    FAIL=$((FAIL+1))
  fi
}
extra() { # extra <cond...> <label>: assert a follow-up condition
  local label="${*: -1}"
  if "${@:1:$#-1}"; then echo "  PASS  $label"; PASS=$((PASS+1))
  else echo "  FAIL  $label"; FAIL=$((FAIL+1)); fi
}

# T1 happy path: PASS, all three captures + manifest, checksums verify,
# benign secret-backend survives unredacted (no false positive).
F="$TMP/good"; mkgood "$F"
run 0 'CLOUD-SNAPSHOT: PASS \(captured 3/3\)' "T1 happy capture -> PASS 3/3" "$F" "$TMP/out1"
D1="$(echo "$TMP"/out1/asbuilt/*)"
extra test -s "$D1/bundle-exported.yaml"  "T1a bundle-exported.yaml written"
extra test -s "$D1/juju-status.json"      "T1b juju-status.json written"
extra test -s "$D1/model-config.yaml"     "T1c model-config.yaml written"
extra grep -q '^redactions: 0$' "$D1/manifest.txt" "T1d manifest reports 0 redactions"
extra grep -q 'secret-backend: auto' "$D1/model-config.yaml" \
  "T1e benign secret-backend NOT redacted (false-positive guard)"
# verify every manifest checksum against the file (inventory is trustworthy)
CHK=0
while read -r sum bytes name; do
  [[ "$sum" =~ ^[0-9a-f]{64}$ ]] || continue
  got="$(cd "$D1" && sha256sum "$name" | awk '{print $1}')"
  [ "$got" = "$sum" ] || CHK=1
  [ "$(wc -c < "$D1/$name" | tr -d ' ')" = "$bytes" ] || CHK=1
done < <(sed -n '/^files:/,$p' "$D1/manifest.txt")
extra test "$CHK" -eq 0 "T1f manifest sha256+size verify against files"

# T2 juju unreachable: HELD, exit 1, NOTHING written
F="$TMP/unreach"; mkgood "$F"; : > "$F/unreach"
run 1 'CLOUD-SNAPSHOT: HELD \(juju unreachable' "T2 juju unreachable -> HELD" "$F" "$TMP/out2"
extra test ! -e "$TMP/out2/asbuilt" "T2a HELD leaves no snapshot dir behind"

# T3 partial capture: export-bundle fails -> counted 2/3, FAIL, manifest states it
F="$TMP/partial"; mkgood "$F"; : > "$F/export-fail"
run 1 'CLOUD-SNAPSHOT: FAIL \(captured 2/3\)' "T3 partial capture counted -> FAIL 2/3" "$F" "$TMP/out3"
D3="$(echo "$TMP"/out3/asbuilt/*)"
extra grep -q '^captured: 2/3$' "$D3/manifest.txt" "T3a manifest records captured 2/3"

# T4 --with-controller-backup: archive under HOME, never in the snapshot dir,
# manifest records it as never-commit; counted 4/4
F="$TMP/backup"; mkgood "$F"
run 0 'CLOUD-SNAPSHOT: PASS \(captured 4/4\)' "T4 --with-controller-backup -> PASS 4/4" "$F" "$TMP/out4" --with-controller-backup
D4="$(echo "$TMP"/out4/asbuilt/*)"
BAK=("$TMP"/out4/home/openstack-baseline/juju-controller-backup-*.tar.gz)
extra test -s "${BAK[0]}" "T4a backup archive lands under HOME/openstack-baseline"
extra bash -c "! ls '$D4'/*.tar.gz >/dev/null 2>&1" "T4b archive NOT inside the committed snapshot dir"
extra grep -q 'NEVER commit' "$D4/manifest.txt" "T4c manifest marks backup never-commit"

# T5 controller backup FAILURE is counted (not silently green)
F="$TMP/bakfail"; mkgood "$F"; : > "$F/backup-fail"
run 1 'CLOUD-SNAPSHOT: FAIL \(captured 3/4\)' "T5 backup failure counted -> FAIL 3/4" "$F" "$TMP/out5" --with-controller-backup

# T6 secret scrub: planted credential-shaped values in bundle AND model-config
# are caught -> WARN exit 2, and the planted values exist NOWHERE in the output
F="$TMP/secret"; mkgood "$F"
cat > "$F/bundle.yaml" <<'Y'
applications:
  rabbitmq-server:
    charm: rabbitmq-server
    options:
      management-password: PlantedSecretValue9911
  keystone:
    charm: keystone
    options:
      region: RegionOne
Y
printf 'default-base: ubuntu@22.04\nsecret-backend: auto\nremote-api-token: "PlantedTokenValue8822"\n' > "$F/model-config.yaml"
run 2 'CLOUD-SNAPSHOT: WARN .*2 redaction' "T6 planted credentials -> WARN with 2 redactions" "$F" "$TMP/out6"
D6="$(echo "$TMP"/out6/asbuilt/*)"
extra bash -c "! grep -rq 'PlantedSecretValue9911\|PlantedTokenValue8822' '$TMP/out6'" \
  "T6a planted values absent from every written byte"
extra grep -q 'REDACTED-BY-CLOUD-SNAPSHOT' "$D6/bundle-exported.yaml" "T6b marker present in scrubbed bundle"
extra grep -q '^redactions: 2$' "$D6/manifest.txt" "T6c manifest records 2 redactions"

echo; echo "RESULT: PASS=$PASS FAIL=$FAIL"
[[ "$FAIL" -eq 0 ]] && { echo "ALL PASS"; exit 0; } || exit 1
