#!/bin/sh set -eu PROJECT_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) PYTHON_BIN="${PYTHON:-$PROJECT_ROOT/.venv/bin/python}" FFMPEG_BIN="${FFMPEG:-$PROJECT_ROOT/vendor/ffmpeg}" FFPROBE_BIN="${FFPROBE:-$PROJECT_ROOT/vendor/ffprobe}" PORT="${1:-18766}" SMOKE_ROOT=$(mktemp -d "${TMPDIR:-/tmp}/imagefind-http-smoke.XXXXXX") DATA_ROOT="$SMOKE_ROOT/data" MEDIA_ROOT="$SMOKE_ROOT/media" COOKIE_JAR="$SMOKE_ROOT/cookies" SERVER_LOG="$SMOKE_ROOT/server.log" PASSWORD="imagefind-http-smoke-password" SERVER_PID="" cleanup() { STATUS=$? trap - EXIT HUP INT TERM if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then kill "$SERVER_PID" 2>/dev/null || true wait "$SERVER_PID" 2>/dev/null || true fi if [ "$STATUS" -ne 0 ]; then printf '%s\n' 'HTTP smoke failed; application log follows:' >&2 tail -100 "$SERVER_LOG" >&2 || true cp -p "$SERVER_LOG" "$PROJECT_ROOT/dist/http-smoke-failure.log" 2>/dev/null || true fi rm -rf -- "$SMOKE_ROOT" exit "$STATUS" } trap cleanup EXIT HUP INT TERM mkdir -p "$DATA_ROOT" "$MEDIA_ROOT" "$FFMPEG_BIN" -hide_banner -loglevel error -f lavfi -i "testsrc=size=320x180:rate=24" \ -t 2 -pix_fmt yuv420p -c:v libx264 -movflags +faststart "$SMOKE_ROOT/ABC-999.mp4" env PYTHONPATH="$PROJECT_ROOT/backend" \ IMAGEFIND_DATA_DIR="$DATA_ROOT" \ IMAGEFIND_FRONTEND_DIR="$PROJECT_ROOT/frontend/dist" \ IMAGEFIND_HOST="127.0.0.1" \ IMAGEFIND_PORT="$PORT" \ IMAGEFIND_UPLOAD_RESERVE_GB="0" \ IMAGEFIND_RESOURCE_DISK_RESERVE_GB="0.5" \ IMAGEFIND_EMBEDDING_BACKEND="hash" \ IMAGEFIND_FFMPEG_PATH="$FFMPEG_BIN" \ IMAGEFIND_FFPROBE_PATH="$FFPROBE_BIN" \ "$PYTHON_BIN" -m imagefind.main >"$SERVER_LOG" 2>&1 & SERVER_PID=$! ATTEMPT=0 until curl -fsS "http://127.0.0.1:$PORT/api/v1/status" >/dev/null 2>&1; do ATTEMPT=$((ATTEMPT + 1)) if [ "$ATTEMPT" -ge 60 ]; then printf '%s\n' 'ImageFind HTTP service did not become ready.' >&2 exit 1 fi sleep 1 done SETUP=$(curl -fsS -c "$COOKIE_JAR" -H 'Content-Type: application/json' \ -d "{\"password\":\"$PASSWORD\"}" "http://127.0.0.1:$PORT/api/v1/setup") CSRF=$(printf '%s' "$SETUP" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["csrf_token"])') SOURCE=$(curl -fsS -b "$COOKIE_JAR" -H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \ -d "{\"name\":\"HTTP Smoke\",\"path\":\"$MEDIA_ROOT\"}" \ "http://127.0.0.1:$PORT/api/v1/sources/local") SOURCE_ID=$(printf '%s' "$SOURCE" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])') curl -fsS -b "$COOKIE_JAR" -H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \ -X PATCH -d '{"writable":true}' \ "http://127.0.0.1:$PORT/api/v1/sources/$SOURCE_ID/writable" >/dev/null SIZE=$(wc -c <"$SMOKE_ROOT/ABC-999.mp4" | tr -d ' ') UPLOAD=$(curl -fsS -b "$COOKIE_JAR" -H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \ -d "{\"source_id\":\"$SOURCE_ID\",\"relative_path\":\"imports\",\"filename\":\"ABC-999.mp4\",\"size_bytes\":$SIZE}" \ "http://127.0.0.1:$PORT/api/v1/uploads") UPLOAD_ID=$(printf '%s' "$UPLOAD" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])') DIGEST=$(sha256sum "$SMOKE_ROOT/ABC-999.mp4" | cut -d ' ' -f 1) curl -fsS -b "$COOKIE_JAR" -H 'Content-Type: application/octet-stream' -H "X-CSRF-Token: $CSRF" \ -H "X-Chunk-SHA256: $DIGEST" -X PUT --data-binary "@$SMOKE_ROOT/ABC-999.mp4" \ "http://127.0.0.1:$PORT/api/v1/uploads/$UPLOAD_ID/chunks/0" >/dev/null curl -fsS -b "$COOKIE_JAR" -H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \ -d '{}' "http://127.0.0.1:$PORT/api/v1/uploads/$UPLOAD_ID/complete" >/dev/null ATTEMPT=0 while :; do UPLOADS=$(curl -fsS -b "$COOKIE_JAR" "http://127.0.0.1:$PORT/api/v1/uploads") STATE=$(printf '%s' "$UPLOADS" | "$PYTHON_BIN" -c \ 'import json,sys; items=json.load(sys.stdin); uid=sys.argv[1]; print(next(x["status"] for x in items if x["id"] == uid))' \ "$UPLOAD_ID") [ "$STATE" = "completed" ] && break [ "$STATE" = "failed" ] && { printf '%s\n' "$UPLOADS" >&2; exit 1; } ATTEMPT=$((ATTEMPT + 1)) [ "$ATTEMPT" -lt 60 ] || { printf '%s\n' "Upload stayed in state $STATE" >&2; exit 1; } sleep 1 done VIDEOS=$(curl -fsS -b "$COOKIE_JAR" "http://127.0.0.1:$PORT/api/v1/videos") VIDEO_ID=$(printf '%s' "$VIDEOS" | "$PYTHON_BIN" -c \ 'import json,sys; print(next(x["id"] for x in json.load(sys.stdin) if x["display_name"] == "ABC-999.mp4"))') curl -fsS -b "$COOKIE_JAR" "http://127.0.0.1:$PORT/api/v1/videos/$VIDEO_ID/download" \ -o "$SMOKE_ROOT/download.mp4" cmp "$SMOKE_ROOT/ABC-999.mp4" "$SMOKE_ROOT/download.mp4" TOKEN_RESPONSE=$(curl -fsS -b "$COOKIE_JAR" -H 'Content-Type: application/json' \ -H "X-CSRF-Token: $CSRF" -d '{"name":"HTTP WebDAV smoke"}' \ "http://127.0.0.1:$PORT/api/v1/tokens") API_TOKEN=$(printf '%s' "$TOKEN_RESPONSE" | "$PYTHON_BIN" -c \ 'import json,sys; print(json.load(sys.stdin)["token"])') curl -fsS -b "$COOKIE_JAR" -H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \ -X PATCH \ -d "{\"enabled\":true,\"source_id\":\"$SOURCE_ID\",\"relative_path\":\"webdav-smoke\"}" \ "http://127.0.0.1:$PORT/api/v1/webdav/config" >/dev/null DAV_SIZE=$SIZE DAV_SPLIT=$((DAV_SIZE / 2)) DAV_REMAINING=$((DAV_SIZE - DAV_SPLIT)) dd if="$SMOKE_ROOT/ABC-999.mp4" of="$SMOKE_ROOT/dav-first.bin" bs=1 count="$DAV_SPLIT" 2>/dev/null dd if="$SMOKE_ROOT/ABC-999.mp4" of="$SMOKE_ROOT/dav-second.bin" bs=1 skip="$DAV_SPLIT" 2>/dev/null DAV_URL="http://127.0.0.1:$PORT/webdav/DAV-RESUME.mp4" FIRST_STATUS=$(curl -sS -u "imagefind:$API_TOKEN" -X PUT \ -H "Content-Range: bytes 0-$((DAV_SPLIT - 1))/$DAV_SIZE" \ -H "Content-Length: $DAV_SPLIT" --data-binary "@$SMOKE_ROOT/dav-first.bin" \ -D "$SMOKE_ROOT/dav-first.headers" -o "$SMOKE_ROOT/dav-first.body" -w '%{http_code}' "$DAV_URL") [ "$FIRST_STATUS" = "204" ] FIRST_OFFSET=$(tr -d '\r' <"$SMOKE_ROOT/dav-first.headers" | awk \ 'tolower($1)=="upload-offset:" {print $2; exit}') [ "$FIRST_OFFSET" = "$DAV_SPLIT" ] HEAD_STATUS=$(curl -sS -u "imagefind:$API_TOKEN" -I \ -D "$SMOKE_ROOT/dav-head.headers" -o /dev/null -w '%{http_code}' "$DAV_URL") [ "$HEAD_STATUS" = "200" ] HEAD_OFFSET=$(tr -d '\r' <"$SMOKE_ROOT/dav-head.headers" | awk \ 'tolower($1)=="upload-offset:" {print $2; exit}') [ "$HEAD_OFFSET" = "$DAV_SPLIT" ] SECOND_STATUS=$(curl -sS -u "imagefind:$API_TOKEN" -X PUT \ -H "Content-Range: bytes $DAV_SPLIT-$((DAV_SIZE - 1))/$DAV_SIZE" \ -H "Content-Length: $DAV_REMAINING" --data-binary "@$SMOKE_ROOT/dav-second.bin" \ -D "$SMOKE_ROOT/dav-second.headers" -o "$SMOKE_ROOT/dav-second.body" -w '%{http_code}' "$DAV_URL") [ "$SECOND_STATUS" = "204" ] DAV_UPLOAD_ID=$(tr -d '\r' <"$SMOKE_ROOT/dav-second.headers" | awk \ 'tolower($1)=="x-imagefind-upload-id:" {print $2; exit}') [ -n "$DAV_UPLOAD_ID" ] ATTEMPT=0 while :; do UPLOADS=$(curl -fsS -b "$COOKIE_JAR" "http://127.0.0.1:$PORT/api/v1/uploads") STATE=$(printf '%s' "$UPLOADS" | "$PYTHON_BIN" -c \ 'import json,sys; items=json.load(sys.stdin); uid=sys.argv[1]; print(next(x["status"] for x in items if x["id"] == uid))' \ "$DAV_UPLOAD_ID") [ "$STATE" = "completed" ] && break [ "$STATE" = "failed" ] && { printf '%s\n' "$UPLOADS" >&2; exit 1; } ATTEMPT=$((ATTEMPT + 1)) [ "$ATTEMPT" -lt 60 ] || { printf '%s\n' "WebDAV upload stayed in state $STATE" >&2; exit 1; } sleep 1 done cmp "$SMOKE_ROOT/ABC-999.mp4" "$MEDIA_ROOT/webdav-smoke/DAV-RESUME.mp4" DEDUP_STATUS=$(curl -sS -u "imagefind:$API_TOKEN" -X PUT \ -H "Content-Length: $DAV_SIZE" -H "X-Content-SHA256: $DIGEST" \ --data-binary "@$SMOKE_ROOT/ABC-999.mp4" -D "$SMOKE_ROOT/dav-dedup.headers" \ -o "$SMOKE_ROOT/dav-dedup.body" -w '%{http_code}' "$DAV_URL") [ "$DEDUP_STATUS" = "204" ] DEDUPLICATED=$(tr -d '\r' <"$SMOKE_ROOT/dav-dedup.headers" | awk \ 'tolower($1)=="x-imagefind-deduplicated:" {print tolower($2); exit}') [ "$DEDUPLICATED" = "true" ] if [ "${IMAGEFIND_RUN_WEBDAV_STRESS:-0}" = "1" ]; then if [ "${IMAGEFIND_RUN_WEBDAV_STRESS_FULL:-0}" = "1" ]; then "$PYTHON_BIN" "$PROJECT_ROOT/scripts/stress-webdav.py" \ --base-url "http://127.0.0.1:$PORT" --token "$API_TOKEN" --workers 6 \ --timeout 7200 --media-prefix "$SMOKE_ROOT/ABC-999.mp4" --full elif [ "${IMAGEFIND_RUN_WEBDAV_STRESS_LARGE:-0}" = "1" ]; then "$PYTHON_BIN" "$PROJECT_ROOT/scripts/stress-webdav.py" \ --base-url "http://127.0.0.1:$PORT" --token "$API_TOKEN" --workers 4 \ --timeout 1800 --media-prefix "$SMOKE_ROOT/ABC-999.mp4" --large else "$PYTHON_BIN" "$PROJECT_ROOT/scripts/stress-webdav.py" \ --base-url "http://127.0.0.1:$PORT" --token "$API_TOKEN" --workers 4 \ --timeout 600 --media-prefix "$SMOKE_ROOT/ABC-999.mp4" fi fi RANGE_STATUS=$(curl -sS -b "$COOKIE_JAR" -H 'Range: bytes=128-1023' \ -o "$SMOKE_ROOT/range.bin" -w '%{http_code}' \ "http://127.0.0.1:$PORT/api/v1/videos/$VIDEO_ID/stream") [ "$RANGE_STATUS" = "206" ] dd if="$SMOKE_ROOT/ABC-999.mp4" of="$SMOKE_ROOT/expected-range.bin" bs=1 skip=128 count=896 2>/dev/null cmp "$SMOKE_ROOT/expected-range.bin" "$SMOKE_ROOT/range.bin" PREVIEW=$(curl -fsS -b "$COOKIE_JAR" -H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \ -d '{"start_ms":0}' "http://127.0.0.1:$PORT/api/v1/videos/$VIDEO_ID/preview") PLAYLIST_URL=$(printf '%s' "$PREVIEW" | "$PYTHON_BIN" -c \ 'import json,sys; print(json.load(sys.stdin)["playlist_url"])') PLAYLIST=$(curl -fsS -b "$COOKIE_JAR" "http://127.0.0.1:$PORT$PLAYLIST_URL") case "$PLAYLIST" in *'#EXTM3U'*'segment-00000.ts'*) ;; *) printf '%s\n' 'HLS playlist did not contain the first segment.' >&2; exit 1 ;; esac printf 'upload=%s video=%s bytes=%s range=206 preview=ok webdav_resume=%s webdav_dedup=true sha256=%s\n' \ "$UPLOAD_ID" "$VIDEO_ID" "$SIZE" "$DAV_UPLOAD_ID" "$DIGEST"