161 lines
5.8 KiB
Python
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())
|