#!/usr/bin/env bash
# Unit test for scripts/release-images.sh — pure resolver, no root/Docker.
set -euo pipefail
here="$(cd "$(dirname "$0")/../.." && pwd)"
# shellcheck source=scripts/release-images.sh disable=SC1091
. "$here/scripts/release-images.sh"

tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
pass=0; fail=0
ok() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1)); printf 'FAIL %s: want=[%s] got=[%s]\n' "$1" "$3" "$2"; fi; }

reset_env() { unset CLUSEV_IMAGE CLUSEV_TERMINAL_IMAGE CLUSEV_PULL CLUSEV_RELEASE_MODE; }
mklock() { printf '{ "version": "v0.11.0", "app": "%s", "terminal": "%s" }\n' "$1" "$2" > "$3"; }
h64() { printf '%064d' 0 | tr 0 "$1"; }  # 64 copies of hex char $1 (a valid sha256 body)

app_ref="ghcr.io/o/clusev@sha256:$(h64 a)"
term_ref="ghcr.io/o/clusev-terminal@sha256:$(h64 b)"
preset_ref="ghcr.io/preset/clusev@sha256:$(h64 c)"

valid_lock="$tmp/valid.lock";   mklock "$app_ref" "$term_ref" "$valid_lock"
empty_lock="$tmp/empty.lock";   mklock "" "$term_ref" "$empty_lock"
slash_lock="$tmp/slash.lock";   mklock "hello/world" "$term_ref" "$slash_lock"
notag_lock="$tmp/notag.lock";   mklock "ghcr.io/o/clusev" "$term_ref" "$notag_lock"
evil_lock="$tmp/evil.lock";     mklock "evil.io/o/clusev@sha256:$(h64 a)" "$term_ref" "$evil_lock"
short_lock="$tmp/short.lock";   mklock "ghcr.io/o/clusev@sha256:aaa" "$term_ref" "$short_lock"
nonhex_lock="$tmp/nonhex.lock"; mklock "ghcr.io/o/clusev@sha256:$(h64 g)" "$term_ref" "$nonhex_lock"
trav_lock="$tmp/trav.lock";     mklock "ghcr.io/o/../clusev@sha256:$(h64 a)" "$term_ref" "$trav_lock"
nl_lock="$tmp/nl.lock";         mklock "$(printf 'ghcr.io/o/clusev@sha256:a\nCLUSEV_PULL=0')" "$term_ref" "$nl_lock"
nlown_lock="$tmp/nlown.lock";   mklock "$(printf 'ghcr.io/o\nEVIL=x/clusev@sha256:%s' "$(h64 a)")" "$term_ref" "$nlown_lock"
dotname_lock="$tmp/dotname.lock"; mklock "ghcr.io/o/..@sha256:$(h64 a)" "$term_ref" "$dotname_lock"

# 1) no lock -> build with local default tags, no pull
reset_env
clusev_resolve_release_images "$tmp/nope.lock"
ok "no-lock mode"     "${CLUSEV_RELEASE_MODE:-}"   "build"
ok "no-lock image"    "${CLUSEV_IMAGE:-}"          "clusev-app:prod"
ok "no-lock terminal" "${CLUSEV_TERMINAL_IMAGE:-}" "clusev-terminal:prod"
ok "no-lock pull"     "${CLUSEV_PULL:-}"           "0"

# 2) valid ghcr 64-hex digest lock -> pull the exact digests
reset_env
clusev_resolve_release_images "$valid_lock"
ok "valid mode"     "${CLUSEV_RELEASE_MODE:-}"   "pull"
ok "valid image"    "${CLUSEV_IMAGE:-}"          "$app_ref"
ok "valid terminal" "${CLUSEV_TERMINAL_IMAGE:-}" "$term_ref"
ok "valid pull"     "${CLUSEV_PULL:-}"           "1"

# 3) empty app ref -> build fallback
reset_env
clusev_resolve_release_images "$empty_lock"
ok "empty mode"  "${CLUSEV_RELEASE_MODE:-}" "build"
ok "empty image" "${CLUSEV_IMAGE:-}"        "clusev-app:prod"
ok "empty pull"  "${CLUSEV_PULL:-}"         "0"

# 4) slash-shaped garbage (has "/", no @sha256) -> build fallback
reset_env
clusev_resolve_release_images "$slash_lock"
ok "slash mode" "${CLUSEV_RELEASE_MODE:-}" "build"

# 5) tag-less ghcr ref (no @sha256:) -> build fallback
reset_env
clusev_resolve_release_images "$notag_lock"
ok "notag mode" "${CLUSEV_RELEASE_MODE:-}" "build"

# 6) non-ghcr host, even 64-hex digest -> build fallback (registry allowlist)
reset_env
clusev_resolve_release_images "$evil_lock"
ok "evil mode"  "${CLUSEV_RELEASE_MODE:-}" "build"
ok "evil image" "${CLUSEV_IMAGE:-}"        "clusev-app:prod"

# 7) short digest (<64 hex) -> build fallback
reset_env
clusev_resolve_release_images "$short_lock"
ok "short mode" "${CLUSEV_RELEASE_MODE:-}" "build"

# 8) non-hex 64-char digest -> build fallback
reset_env
clusev_resolve_release_images "$nonhex_lock"
ok "nonhex mode" "${CLUSEV_RELEASE_MODE:-}" "build"

# 9) extra path segment (…/../…) -> build fallback (no traversal-shaped ref pulled)
reset_env
clusev_resolve_release_images "$trav_lock"
ok "trav mode" "${CLUSEV_RELEASE_MODE:-}" "build"

# 10) newline-injecting digest ref -> build fallback (never reaches force_kv -> no .env injection)
reset_env
clusev_resolve_release_images "$nl_lock"
ok "nl mode"  "${CLUSEV_RELEASE_MODE:-}" "build"
ok "nl image" "${CLUSEV_IMAGE:-}"        "clusev-app:prod"

# 11) newline embedded in the OWNER segment (not the digest) -> build; never reaches force_kv
reset_env
clusev_resolve_release_images "$nlown_lock"
ok "nlown mode"  "${CLUSEV_RELEASE_MODE:-}" "build"
ok "nlown image" "${CLUSEV_IMAGE:-}"        "clusev-app:prod"

# 12) relative/dotted image name (..) -> build (no traversal-shaped ref pulled)
reset_env
clusev_resolve_release_images "$dotname_lock"
ok "dotname mode" "${CLUSEV_RELEASE_MODE:-}" "build"

# 13) env preset wins over lock; terminal defaults when unset
reset_env
export CLUSEV_IMAGE="$preset_ref"
clusev_resolve_release_images "$valid_lock"
ok "env mode"     "${CLUSEV_RELEASE_MODE:-}"   "env"
ok "env image"    "${CLUSEV_IMAGE:-}"          "$preset_ref"
ok "env terminal" "${CLUSEV_TERMINAL_IMAGE:-}" "clusev-terminal:prod"
ok "env pull"     "${CLUSEV_PULL:-}"           "1"

# 14) json_str: known + missing key
reset_env
ok "json known"   "$(clusev_json_str "$valid_lock" app)"  "$app_ref"
ok "json missing" "$(clusev_json_str "$valid_lock" nope)" ""

printf '\n%d passed, %d failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]
