110 lines
4.1 KiB
Bash
Executable File
110 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [ -z "${BASE_URL:-}" ]; then
|
|
if curl --silent --fail http://127.0.0.1:8085/api/health >/dev/null 2>&1; then
|
|
BASE_URL="http://127.0.0.1:8085"
|
|
elif curl --silent --fail http://host.containers.internal:8085/api/health >/dev/null 2>&1; then
|
|
BASE_URL="http://host.containers.internal:8085"
|
|
else
|
|
echo "ERROR: could not determine BASE_URL. Tried 127.0.0.1 and host.containers.internal." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
|
|
echo "== Feature test 1: roots endpoint returns configured roots with stable fields =="
|
|
curl --fail --silent --show-error \
|
|
"${BASE_URL}/api/files/roots" \
|
|
-o "${TMP_DIR}/roots.json"
|
|
|
|
cat "${TMP_DIR}/roots.json"
|
|
|
|
python3 - "${TMP_DIR}/roots.json" > "${TMP_DIR}/root_id.txt" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
items = data.get("items")
|
|
assert isinstance(items, list), "roots.items must be a list"
|
|
assert len(items) > 0, "roots.items must not be empty"
|
|
first = items[0]
|
|
for key in ["id", "path", "exists", "readable"]:
|
|
assert key in first, f"root missing key: {key}"
|
|
assert isinstance(first["id"], str) and first["id"], "root.id must be non-empty string"
|
|
assert isinstance(first["path"], str) and first["path"], "root.path must be non-empty string"
|
|
assert isinstance(first["exists"], bool), "root.exists must be boolean"
|
|
assert isinstance(first["readable"], bool), "root.readable must be boolean"
|
|
print(first["id"])
|
|
PY
|
|
|
|
ROOT_ID="$(cat "${TMP_DIR}/root_id.txt")"
|
|
|
|
echo
|
|
echo "== Feature test 2: discover within root returns response shape and allowed extensions only =="
|
|
curl --fail --silent --show-error \
|
|
"${BASE_URL}/api/files/discover?root_id=${ROOT_ID}&subpath=&recursive=false&limit=200" \
|
|
-o "${TMP_DIR}/discover.json"
|
|
|
|
cat "${TMP_DIR}/discover.json"
|
|
|
|
python3 - "${TMP_DIR}/discover.json" "$ROOT_ID" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
root_id = sys.argv[2]
|
|
assert data.get("root_id") == root_id, "discover.root_id mismatch"
|
|
assert "root_path" in data and isinstance(data["root_path"], str), "discover.root_path missing/invalid"
|
|
assert "items" in data and isinstance(data["items"], list), "discover.items missing/invalid"
|
|
|
|
allowed = {".mkv", ".mp4", ".avi", ".m4v", ".srt"}
|
|
for item in data["items"]:
|
|
for key in ["name", "path", "relative_path", "extension", "size_bytes", "modified_at_unix"]:
|
|
assert key in item, f"discover item missing key: {key}"
|
|
assert item["extension"] in allowed, f"unexpected extension: {item['extension']}"
|
|
|
|
print("discover shape validation passed")
|
|
PY
|
|
|
|
echo
|
|
echo "== Feature test 3: traversal/absolute subpath is rejected =="
|
|
curl --silent --show-error \
|
|
-o "${TMP_DIR}/discover_bad_abs.json" \
|
|
-w "%{http_code}" \
|
|
"${BASE_URL}/api/files/discover?root_id=${ROOT_ID}&subpath=/etc&recursive=false&limit=10" \
|
|
> "${TMP_DIR}/discover_bad_abs.status"
|
|
|
|
curl --silent --show-error \
|
|
-o "${TMP_DIR}/discover_bad_parent.json" \
|
|
-w "%{http_code}" \
|
|
"${BASE_URL}/api/files/discover?root_id=${ROOT_ID}&subpath=../secret&recursive=false&limit=10" \
|
|
> "${TMP_DIR}/discover_bad_parent.status"
|
|
|
|
cat "${TMP_DIR}/discover_bad_abs.json"
|
|
cat "${TMP_DIR}/discover_bad_parent.json"
|
|
|
|
python3 - "${TMP_DIR}/discover_bad_abs.status" "${TMP_DIR}/discover_bad_abs.json" "${TMP_DIR}/discover_bad_parent.status" "${TMP_DIR}/discover_bad_parent.json" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
status_abs = Path(sys.argv[1]).read_text(encoding="utf-8").strip()
|
|
json_abs = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
|
|
status_parent = Path(sys.argv[3]).read_text(encoding="utf-8").strip()
|
|
json_parent = json.loads(Path(sys.argv[4]).read_text(encoding="utf-8"))
|
|
|
|
assert status_abs == "400", f"absolute subpath should be 400, got {status_abs}"
|
|
assert status_parent == "400", f"parent traversal should be 400, got {status_parent}"
|
|
assert "detail" in json_abs, "absolute subpath response missing detail"
|
|
assert "detail" in json_parent, "parent traversal response missing detail"
|
|
print("subpath security validation passed")
|
|
PY
|
|
|
|
echo
|
|
echo "All file discovery feature tests passed."
|