Files
Nexus/tests/test_full_site_api.py
T
2026-07-08 22:31:31 +08:00

161 lines
5.8 KiB
Python

"""
Nexus full-site API coverage (read-mostly + isolated CRUD with cleanup).
Runs after test_api.py patterns; safe on production when CRUD uses e2e-* names only.
Usage: python tests/test_full_site_api.py
Env: same as tests/test_api.py (loads .env NEXUS_TEST_*)
"""
from __future__ import annotations
import json
import os
import sys
import urllib.error
import urllib.request
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import test_api as base # noqa: E402
base._load_credentials_from_env_file()
def _headers():
h = {"Content-Type": "application/json"}
if base.ACCESS_TOKEN:
h["Authorization"] = f"Bearer {base.ACCESS_TOKEN}"
return h
PASS = 0
FAIL = 0
def call(name: str, method: str, path: str, body=None, expect=200):
global PASS, FAIL
url = f"{base.BASE}{path}"
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, method=method, headers=_headers())
try:
with urllib.request.urlopen(req, timeout=30) as resp:
code = resp.getcode()
raw = resp.read()
if code == expect:
PASS += 1
print(f" [PASS] {name}")
if not raw:
return {}
return json.loads(raw)
FAIL += 1
print(f" [FAIL] {name}: expected {expect}, got {code}")
except urllib.error.HTTPError as e:
if e.code == expect:
PASS += 1
print(f" [PASS] {name}")
body_b = e.read()
return json.loads(body_b) if body_b else {}
FAIL += 1
print(f" [FAIL] {name}: HTTP {e.code}{e.read().decode()[:180]}")
except Exception as ex:
FAIL += 1
print(f" [FAIL] {name}: {ex}")
return None
def main() -> int:
global PASS, FAIL
print("=" * 50)
print("Nexus Full-Site API Tests")
print(f" Target: {base.BASE}")
print("=" * 50)
base._load_credentials_from_env_file()
base.api_test_plain_text("GET /health", "GET", "/health", expect_body="ok")
login = base.api_test("POST /api/auth/login", "POST", "/api/auth/login", body={
"username": base.ADMIN_USER,
"password": base.ADMIN_PASSWORD,
}, expect_code=200)
if not login or not login.get("access_token"):
print("FATAL: login failed")
return 1
base.ACCESS_TOKEN = login["access_token"]
base.REFRESH_TOKEN = login.get("refresh_token", "")
print("\n[Search & Alerts]")
call("GET /api/search/", "GET", "/api/search/?q=test")
call("GET /api/alert-history/", "GET", "/api/alert-history/?limit=5")
call("GET /api/alert-history/stats", "GET", "/api/alert-history/stats")
print("\n[Assets]")
call("GET /api/assets/platforms", "GET", "/api/assets/platforms")
call("GET /api/assets/nodes", "GET", "/api/assets/nodes")
call("GET /api/assets/nodes/tree", "GET", "/api/assets/nodes/tree")
call("GET /api/assets/ssh-sessions", "GET", "/api/assets/ssh-sessions?limit=5")
call("GET /api/assets/command-logs", "GET", "/api/assets/command-logs?limit=5")
plat = call("POST /api/assets/platforms (e2e)", "POST", "/api/assets/platforms", body={
"name": "e2e-platform-full",
"category": "test",
"type": "linux",
}, expect=201)
plat_id = plat.get("id") if plat else None
if plat_id:
call(f"PUT /api/assets/platforms/{plat_id}", "PUT", f"/api/assets/platforms/{plat_id}", body={
"description": "updated",
})
call(f"DELETE /api/assets/platforms/{plat_id}", "DELETE", f"/api/assets/platforms/{plat_id}", expect=204)
print("\n[Servers meta]")
call("GET /api/servers/categories", "GET", "/api/servers/categories")
call("GET /api/servers/logs", "GET", "/api/servers/logs?per_page=5")
call("GET /api/servers/meta/api_base_url", "GET", "/api/servers/meta/api_base_url")
servers = call("GET /api/servers/", "GET", "/api/servers/?per_page=5")
first_id = None
if servers:
items = servers.get("items") or servers.get("servers") or []
if items and isinstance(items[0], dict):
first_id = items[0].get("id")
if first_id:
call(f"GET /api/servers/{first_id}/logs", "GET", f"/api/servers/{first_id}/logs?limit=3")
print("\n[Scripts & credentials]")
call("GET /api/scripts/exec-config", "GET", "/api/scripts/exec-config")
call("GET /api/scripts/executions", "GET", "/api/scripts/executions?limit=5")
call("GET /api/scripts/credentials", "GET", "/api/scripts/credentials")
call("GET /api/ssh-key-presets/", "GET", "/api/ssh-key-presets/")
print("\n[Sync read-only / safe]")
call("POST /api/sync/validate-source-path", "POST", "/api/sync/validate-source-path", body={
"path": "/tmp",
})
if first_id:
call("POST /api/sync/browse (real server)", "POST", "/api/sync/browse", body={
"server_id": first_id,
"path": "/tmp",
})
call("POST /api/auth/webssh-token", "POST", "/api/auth/webssh-token", body={
"server_id": first_id,
})
print("\n[Settings & health detail]")
call("GET /api/settings/", "GET", "/api/settings/")
call("GET /api/settings/ip-allowlist", "GET", "/api/settings/ip-allowlist")
call("GET /api/settings/bing-wallpapers", "GET", "/api/settings/bing-wallpapers")
call("GET /health/detail", "GET", "/health/detail")
print("\n[Terminal]")
call("GET /api/terminal/quick-commands", "GET", "/api/terminal/quick-commands")
print("\n[Auth refresh]")
if base.REFRESH_TOKEN:
call("POST /api/auth/refresh", "POST", "/api/auth/refresh", body={
"refresh_token": base.REFRESH_TOKEN,
})
print(f"\n{'=' * 50}")
print(f"Full-site API: {PASS}/{PASS + FAIL} passed, {FAIL} failed")
return 0 if FAIL == 0 else 1
if __name__ == "__main__":
sys.exit(main())