Files
tianxuan/tests/test_e2e_v1.1.4.py
T

230 lines
6.9 KiB
Python

"""E2E test v1.1.4: upload 10 CSVs → cluster → verify globe.
Full pipeline WITHOUT manual server start/stop.
Port 8766 avoids conflicts with 8000.
Uses stdlib urllib (no pip install needed).
"""
import subprocess
import time
import sys
import os
import glob
import json
import uuid
import urllib.request
import urllib.error
PORT = 8766
BASE = f"http://127.0.0.1:{PORT}"
PROJ = r"C:\Users\25044\Desktop\Proj\天璇"
def http_get(path, timeout=30):
"""GET and return (status_code, body_bytes)."""
try:
r = urllib.request.urlopen(f"{BASE}{path}", timeout=timeout)
body = r.read()
code = r.status
r.close()
return code, body
except urllib.error.HTTPError as e:
return e.code, e.read()
def http_post_json(path, data_dict, timeout=30):
"""POST JSON and return (status_code, parsed_json)."""
body = json.dumps(data_dict).encode("utf-8")
req = urllib.request.Request(
f"{BASE}{path}", data=body, headers={"Content-Type": "application/json"}
)
try:
r = urllib.request.urlopen(req, timeout=timeout)
return r.status, json.loads(r.read())
except urllib.error.HTTPError as e:
return e.code, json.loads(e.read())
def http_post_files(path, file_paths, timeout=120):
"""POST multipart/form-data upload. Returns (status_code, parsed_json)."""
boundary = uuid.uuid4().hex
lines = []
for fp in file_paths:
fname = os.path.basename(fp)
with open(fp, "rb") as f:
filedata = f.read()
lines.append(f"--{boundary}".encode())
lines.append(
f'Content-Disposition: form-data; name="files"; filename="{fname}"'.encode()
)
lines.append(b"Content-Type: text/csv")
lines.append(b"")
lines.append(filedata)
lines.append(f"--{boundary}--".encode())
body = b"\r\n".join(lines)
req = urllib.request.Request(
f"{BASE}{path}",
data=body,
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
)
try:
r = urllib.request.urlopen(req, timeout=timeout)
return r.status, json.loads(r.read())
except urllib.error.HTTPError as e:
return e.code, json.loads(e.read())
# ── 1. Start server ──
print("[1/6] Starting Django server...")
import tempfile as _tmp
_stderr_log = os.path.join(PROJ, "tests", "_server_stderr.log")
_stderr_fh = open(_stderr_log, "w")
server = subprocess.Popen(
[
os.path.join(PROJ, "runtime", "python", "python.exe"),
"manage.py",
"runserver",
f"127.0.0.1:{PORT}",
"--noreload",
],
cwd=PROJ,
stdout=_stderr_fh,
stderr=subprocess.STDOUT,
)
time.sleep(8)
# Verify server is up
for attempt in range(10):
try:
urllib.request.urlopen(f"{BASE}/", timeout=3)
print(" Server is up.")
break
except Exception:
if attempt == 9:
print(" FAIL: Server did not start within 30 seconds")
server.terminate()
server.wait()
sys.exit(1)
time.sleep(2)
# ── 2. Upload 10 test_*.csv files ──
csv_files = sorted(glob.glob(os.path.join(PROJ, "data", "multi_upload", "test_*.csv")))
print(f"[2/6] Found {len(csv_files)} CSV files. Uploading first 10...")
batch = csv_files[:10]
code, data = http_post_files("/upload/csv/", batch, timeout=120)
if code != 200:
print(f" FAIL: Upload returned HTTP {code}: {data}")
server.terminate()
server.wait()
sys.exit(1)
run_id = data["run_id"]
print(f" Uploaded 10 files → run_id={run_id}, status={data['status']}")
# ── 3. Poll until upload processing completes (status='ready') ──
print("[3/6] Waiting for background processing...")
s = {}
for i in range(180):
code, body = http_get(f"/runs/{run_id}/status/", timeout=10)
s = json.loads(body) if code == 200 else {}
if s.get("status") in ("ready", "failed"):
break
if i % 10 == 0:
print(f" Poll #{i}: {s.get('status')} ({s.get('progress_pct', 0)}%)")
time.sleep(2)
print(f" Status: {s.get('status')}, progress={s.get('progress_pct')}%")
if s.get("status") == "failed":
print(f" FAIL: {s.get('error_message', '')[:500]}")
server.terminate()
server.wait()
sys.exit(1)
if s.get("status") != "ready":
print(f" FAIL: Timed out waiting for 'ready', got '{s.get('status')}'")
server.terminate()
server.wait()
sys.exit(1)
# ── 4. Run clustering ──
print("[4/6] Starting clustering (HDBSCAN, min_cluster_size=5)...")
code, resp = http_post_json(
"/analyze/run/",
{"run_id": run_id, "algorithm": "hdbscan", "min_cluster_size": 5, "cluster_mode": "raw"},
timeout=30,
)
print(f" Response HTTP {code}: {resp.get('status')}")
if code != 200:
print(f" FAIL: Clustering start failed: {resp}")
server.terminate()
server.wait()
sys.exit(1)
# ── 5. Poll until clustering completes ──
print("[5/6] Waiting for clustering...")
for i in range(120):
code, body = http_get(f"/runs/{run_id}/status/", timeout=10)
s = json.loads(body) if code == 200 else {}
if s.get("status") in ("completed", "failed"):
break
if i % 5 == 0:
print(
f" Poll #{i}: {s.get('status')} ({s.get('progress_pct', 0)}%)"
f" - {s.get('progress_msg', '')}"
)
time.sleep(3)
print(
f" Final: status={s.get('status')}, entity_count={s.get('entity_count')},"
f" cluster_count={s.get('cluster_count')}"
)
# ── 6. Verify globe page ──
print("[6/6] Verifying globe page...")
code, body = http_get(f"/globe/?runs={run_id}", timeout=30)
globe_text = body.decode("utf-8", errors="replace")
has_arcs = "arcCount" in globe_text
print(f" Globe HTTP {code}, arcCount present: {has_arcs}")
# ── Cleanup ──
server.terminate()
server.wait()
_stderr_fh.close()
# Print last 30 lines of server output on failure
_need_server_log = False
# ── Report ──
errors = []
if s.get("status") != "completed":
errors.append(f"Expected status='completed', got '{s.get('status')}'")
errors.append(f"error: {s.get('error_message', '')[:500]}")
if s.get("cluster_count", 0) == 0:
errors.append("No clusters found (cluster_count=0)")
if not has_arcs:
errors.append("Globe page missing 'arcCount' element")
if errors:
print(f"\nFAIL ({len(errors)} errors):")
for e in errors:
print(f" - {e}")
# Show server stderr tail
if os.path.exists(_stderr_log):
with open(_stderr_log, "r", encoding="utf-8", errors="replace") as f:
lines = f.readlines()
tail = lines[-30:] if len(lines) > 30 else lines
print(f"\n--- Server stderr (last {len(tail)} lines) ---")
for line in tail:
print(f" {line.rstrip()}")
sys.exit(1)
print("\n*** E2E v1.1.4 PASSED ***")
print(
f" run_id={run_id}, clusters={s.get('cluster_count')},"
f" entities={s.get('entity_count')}, globe_arcs={'yes' if has_arcs else 'no'}"
)