Add local self-test flow and fix fallback regressions
build-push / docker (push) Successful in 4m15s

This commit is contained in:
AI Assistant
2026-03-13 18:09:32 +09:00
parent f968458c5c
commit 6f3149a443
7 changed files with 373 additions and 69 deletions
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import argparse
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse
def build_results(engine: str, query: str):
lowered = (engine or "").lower()
if "google" in lowered and "video" in lowered:
return [
{
"title": f"{query.title()} cinematic b-roll",
"url": "https://www.youtube.com/watch?v=abcdefghijk",
"content": f"{query} stock footage edit reference",
"thumbnail": "https://i.ytimg.com/vi/abcdefghijk/hqdefault.jpg",
"engine": engine,
}
]
return [
{
"title": f"{query.title()} envato clip",
"url": "https://elements.envato.com/city-rain-b-roll-AB12CD",
"content": f"{query} stock footage cinematic scene",
"thumbnail": "https://images.example.com/envato-city-rain.jpg",
"thumbnail_src": "https://images.example.com/envato-city-rain.jpg",
"engine": engine,
},
{
"title": f"{query.title()} artgrid clip",
"url": "https://artgrid.io/clip/123456/city-rain-night",
"content": f"{query} editorial footage establishing shot",
"thumbnail": "https://images.example.com/artgrid-city-rain.jpg",
"thumbnail_src": "https://images.example.com/artgrid-city-rain.jpg",
"img_src": "https://images.example.com/artgrid-city-rain.jpg",
"engine": engine,
},
]
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
if parsed.path != "/search":
self.send_response(404)
self.end_headers()
return
params = parse_qs(parsed.query)
query = params.get("q", ["city rain"])[0]
engine = params.get("engines", [""])[0]
payload = {"results": build_results(engine, query)}
body = json.dumps(payload).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, format, *args):
return
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int, default=18080)
args = parser.parse_args()
server = ThreadingHTTPServer(("127.0.0.1", args.port), Handler)
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
if __name__ == "__main__":
main()
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TMP_DIR="$(mktemp -d)"
MOCK_PORT="${SELFTEST_MOCK_PORT:-18080}"
APP_PORT="${SELFTEST_APP_PORT:-18081}"
MOCK_PID=""
APP_PID=""
cleanup() {
if [[ -n "${APP_PID}" ]] && kill -0 "${APP_PID}" >/dev/null 2>&1; then
kill "${APP_PID}" >/dev/null 2>&1 || true
wait "${APP_PID}" >/dev/null 2>&1 || true
fi
if [[ -n "${MOCK_PID}" ]] && kill -0 "${MOCK_PID}" >/dev/null 2>&1; then
kill "${MOCK_PID}" >/dev/null 2>&1 || true
wait "${MOCK_PID}" >/dev/null 2>&1 || true
fi
rm -rf "${TMP_DIR}"
}
trap cleanup EXIT
cd "${ROOT_DIR}"
echo "[selftest] gofmt"
gofmt -w backend/main.go backend/handlers/api.go backend/models/db.go backend/services/cse.go backend/services/gemini.go backend/services/gemini_test.go
echo "[selftest] python syntax"
python3 -m py_compile worker/downloader.py scripts/mock_searxng.py
echo "[selftest] go test"
go test ./...
echo "[selftest] go build"
go build -o "${TMP_DIR}/ai-media-hub" ./backend
echo "[selftest] start mock searxng"
python3 scripts/mock_searxng.py --port "${MOCK_PORT}" >"${TMP_DIR}/mock-searxng.log" 2>&1 &
MOCK_PID=$!
echo "[selftest] start app"
mkdir -p "${TMP_DIR}/downloads"
APP_ROOT="${ROOT_DIR}" \
APP_ADDR="127.0.0.1:${APP_PORT}" \
SQLITE_PATH="${TMP_DIR}/media.db" \
DOWNLOADS_DIR="${TMP_DIR}/downloads" \
FRONTEND_DIR="${ROOT_DIR}/frontend" \
WORKER_SCRIPT="${ROOT_DIR}/worker/downloader.py" \
SEARXNG_BASE_URL="http://127.0.0.1:${MOCK_PORT}" \
"${TMP_DIR}/ai-media-hub" >"${TMP_DIR}/app.log" 2>&1 &
APP_PID=$!
for _ in $(seq 1 30); do
if curl -fsS "http://127.0.0.1:${APP_PORT}/healthz" >/dev/null; then
break
fi
sleep 1
done
echo "[selftest] verify healthz"
curl -fsS "http://127.0.0.1:${APP_PORT}/healthz" >"${TMP_DIR}/healthz.json"
python3 - "${TMP_DIR}/healthz.json" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as handle:
payload = json.load(handle)
if payload.get("status") != "ok":
raise SystemExit(f"unexpected healthz payload: {payload}")
PY
echo "[selftest] verify search"
curl -fsS \
-H "Content-Type: application/json" \
-d '{"query":"city rain","platforms":["envato","artgrid","google video"]}' \
"http://127.0.0.1:${APP_PORT}/api/search" >"${TMP_DIR}/search.json"
python3 - "${TMP_DIR}/search.json" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as handle:
payload = json.load(handle)
results = payload.get("results") or []
if len(results) < 2:
raise SystemExit(f"expected >= 2 search results, got {len(results)}: {payload}")
if not all(item.get("link") for item in results):
raise SystemExit(f"search results must include links: {payload}")
PY
echo "[selftest] verify upload"
printf 'selftest upload\n' > "${TMP_DIR}/sample.txt"
curl -fsS -F "file=@${TMP_DIR}/sample.txt" "http://127.0.0.1:${APP_PORT}/api/upload" >"${TMP_DIR}/upload.json"
python3 - "${TMP_DIR}/upload.json" "${TMP_DIR}/downloads" <<'PY'
import json
import os
import sys
with open(sys.argv[1], "r", encoding="utf-8") as handle:
payload = json.load(handle)
filename = payload.get("filename")
if not filename:
raise SystemExit(f"missing filename in upload payload: {payload}")
target = os.path.join(sys.argv[2], filename)
if not os.path.exists(target):
raise SystemExit(f"expected uploaded file at {target}")
PY
echo "[selftest] ok"