feat: add ImageFind application and release pipelines
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only live acceptance for upload pagination and core API latency."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import statistics
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
def call(client: httpx.Client, path: str) -> tuple[Any, float]:
|
||||
started = time.perf_counter()
|
||||
response = client.get(path)
|
||||
elapsed_ms = (time.perf_counter() - started) * 1000
|
||||
if response.is_error:
|
||||
raise RuntimeError(f"GET {path} -> {response.status_code}: {response.text[:500]}")
|
||||
return response.json(), elapsed_ms
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--base-url", required=True)
|
||||
auth = parser.add_mutually_exclusive_group(required=True)
|
||||
auth.add_argument("--token-file", type=Path)
|
||||
auth.add_argument("--password-stdin", action="store_true")
|
||||
parser.add_argument("--report", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
headers: dict[str, str] = {}
|
||||
if args.token_file:
|
||||
headers["Authorization"] = f"Bearer {args.token_file.read_text(encoding='utf-8').strip()}"
|
||||
client = httpx.Client(
|
||||
base_url=args.base_url.rstrip("/"),
|
||||
headers=headers,
|
||||
timeout=httpx.Timeout(30, connect=10),
|
||||
)
|
||||
if args.password_stdin:
|
||||
password = sys.stdin.readline().rstrip("\r\n")
|
||||
login = client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={"password": password, "remember_device": False},
|
||||
)
|
||||
password = ""
|
||||
if login.is_error:
|
||||
raise RuntimeError(f"login -> {login.status_code}: {login.text[:500]}")
|
||||
|
||||
status, status_ms = call(client, "/api/v1/status")
|
||||
if status.get("version") != "0.5.45" or not status.get("configured"):
|
||||
raise AssertionError(f"unexpected status: {status}")
|
||||
|
||||
legacy, legacy_ms = call(client, "/api/v1/uploads?limit=4")
|
||||
if not isinstance(legacy, list) or len(legacy) > 4:
|
||||
raise AssertionError("legacy upload response is no longer a bounded array")
|
||||
|
||||
first, first_ms = call(client, "/api/v1/uploads?page=1&page_size=10")
|
||||
required = {
|
||||
"items",
|
||||
"status_items",
|
||||
"active_count",
|
||||
"failed_count",
|
||||
"page",
|
||||
"page_size",
|
||||
"total",
|
||||
"pages",
|
||||
}
|
||||
if not isinstance(first, dict) or not required.issubset(first):
|
||||
actual = sorted(first) if isinstance(first, dict) else type(first)
|
||||
raise AssertionError(f"upload page contract mismatch: {actual}")
|
||||
if first["page"] != 1 or first["page_size"] != 10 or len(first["items"]) > 10:
|
||||
raise AssertionError("upload first page bounds are invalid")
|
||||
if first["pages"] < 1 or first["total"] < len(first["items"]):
|
||||
raise AssertionError("upload pagination totals are invalid")
|
||||
order = [(str(item.get("created_at") or ""), str(item["id"])) for item in first["items"]]
|
||||
if order != sorted(order, reverse=True):
|
||||
raise AssertionError("upload page ordering is unstable")
|
||||
allowed_statuses = {"receiving", "queued", "transferring", "indexing", "failed"}
|
||||
if any(item.get("status") not in allowed_statuses for item in first["status_items"]):
|
||||
raise AssertionError("upload status summary contains terminal history")
|
||||
active_visible = sum(item.get("status") != "failed" for item in first["status_items"])
|
||||
failed_visible = sum(item.get("status") == "failed" for item in first["status_items"])
|
||||
if first["active_count"] < active_visible or first["failed_count"] < failed_visible:
|
||||
raise AssertionError("upload status counters are smaller than their visible summaries")
|
||||
|
||||
second_ids: list[str] = []
|
||||
second_ms: float | None = None
|
||||
if first["pages"] > 1:
|
||||
second, second_ms = call(client, "/api/v1/uploads?page=2&page_size=10")
|
||||
second_ids = [str(item["id"]) for item in second["items"]]
|
||||
if set(second_ids) & {str(item["id"]) for item in first["items"]}:
|
||||
raise AssertionError("upload pages overlap")
|
||||
|
||||
latency_paths = {
|
||||
"uploads": "/api/v1/uploads?page=1&page_size=10",
|
||||
"videos": "/api/v1/videos?limit=20",
|
||||
"profile": "/api/v1/profile",
|
||||
"resources": "/api/v1/system/resources",
|
||||
}
|
||||
latency: dict[str, dict[str, float]] = {}
|
||||
resource_state: dict[str, Any] = {}
|
||||
for name, path in latency_paths.items():
|
||||
samples: list[float] = []
|
||||
value: Any = None
|
||||
for _ in range(3):
|
||||
value, elapsed_ms = call(client, path)
|
||||
samples.append(elapsed_ms)
|
||||
latency[name] = {
|
||||
"median_ms": round(statistics.median(samples), 2),
|
||||
"max_ms": round(max(samples), 2),
|
||||
}
|
||||
if name == "resources" and isinstance(value, dict):
|
||||
database = value.get("database") or {}
|
||||
resource_state = {
|
||||
"cpu_percent": value.get("cpu_percent"),
|
||||
"memory_available_bytes": value.get("memory_available_bytes"),
|
||||
"running_jobs": value.get("running_jobs"),
|
||||
"paused_jobs": value.get("paused_jobs"),
|
||||
"database": {
|
||||
"engine": database.get("engine"),
|
||||
"pool_in_use": database.get("pool_in_use"),
|
||||
"pool_waiters": database.get("pool_waiters"),
|
||||
"slow_transaction_count": database.get("slow_transaction_count"),
|
||||
},
|
||||
}
|
||||
|
||||
report = {
|
||||
"status": status,
|
||||
"contract": {
|
||||
"legacy_count": len(legacy),
|
||||
"page": first["page"],
|
||||
"page_size": first["page_size"],
|
||||
"page_items": len(first["items"]),
|
||||
"total": first["total"],
|
||||
"pages": first["pages"],
|
||||
"second_page_items": len(second_ids),
|
||||
"status_items": len(first["status_items"]),
|
||||
"active_count": first["active_count"],
|
||||
"failed_count": first["failed_count"],
|
||||
},
|
||||
"initial_latency_ms": {
|
||||
"status": round(status_ms, 2),
|
||||
"legacy_uploads": round(legacy_ms, 2),
|
||||
"first_page": round(first_ms, 2),
|
||||
"second_page": round(second_ms, 2) if second_ms is not None else None,
|
||||
},
|
||||
"latency": latency,
|
||||
"resources": resource_state,
|
||||
}
|
||||
encoded = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
|
||||
if args.report:
|
||||
args.report.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.report.write_text(encoded, encoding="utf-8")
|
||||
print(encoded, end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user