Files
light-heart-labs--dreamserver/ods/extensions/services/dashboard-api/tests/test_setup.py
T
wehub-resource-sync 9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:33 +08:00

277 lines
10 KiB
Python

"""Tests for routers/setup.py — setup wizard, persona selection, and completion."""
import json
# ---------------------------------------------------------------------------
# Auth enforcement — 401 without Bearer token
# ---------------------------------------------------------------------------
def test_setup_persona_requires_auth(test_client):
resp = test_client.post("/api/setup/persona", json={"persona": "general"})
assert resp.status_code == 401
def test_setup_complete_requires_auth(test_client):
resp = test_client.post("/api/setup/complete")
assert resp.status_code == 401
def test_list_personas_requires_auth(test_client):
resp = test_client.get("/api/setup/personas")
assert resp.status_code == 401
def test_get_persona_info_requires_auth(test_client):
resp = test_client.get("/api/setup/persona/general")
assert resp.status_code == 401
def test_setup_diagnostics_requires_auth(test_client):
resp = test_client.post("/api/setup/test")
assert resp.status_code == 401
# ---------------------------------------------------------------------------
# GET /api/setup/status
# ---------------------------------------------------------------------------
def test_setup_status_first_run(test_client, setup_config_dir):
"""When setup-complete.json is absent, first_run is True and step is 0."""
resp = test_client.get("/api/setup/status", headers=test_client.auth_headers)
assert resp.status_code == 200
data = resp.json()
assert data["first_run"] is True
assert data["step"] == 0
assert data["persona"] is None
def test_setup_status_already_complete(test_client, setup_config_dir):
"""When setup-complete.json exists, first_run is False."""
(setup_config_dir / "setup-complete.json").write_text(
json.dumps({"completed_at": "2025-01-01T00:00:00+00:00", "version": "1.0.0"})
)
resp = test_client.get("/api/setup/status", headers=test_client.auth_headers)
assert resp.status_code == 200
assert resp.json()["first_run"] is False
def test_setup_status_with_progress_step(test_client, setup_config_dir):
"""Progress step is read from setup-progress.json."""
(setup_config_dir / "setup-progress.json").write_text(
json.dumps({"step": 2, "persona_selected": True})
)
resp = test_client.get("/api/setup/status", headers=test_client.auth_headers)
assert resp.status_code == 200
assert resp.json()["step"] == 2
def test_setup_status_with_active_persona(test_client, setup_config_dir):
"""Active persona ID is returned when persona.json exists."""
(setup_config_dir / "persona.json").write_text(
json.dumps({"persona": "coding", "name": "Coding Buddy"})
)
resp = test_client.get("/api/setup/status", headers=test_client.auth_headers)
assert resp.status_code == 200
assert resp.json()["persona"] == "coding"
def test_setup_status_includes_personas_available(test_client, setup_config_dir):
"""personas_available lists at least the built-in persona keys."""
resp = test_client.get("/api/setup/status", headers=test_client.auth_headers)
assert resp.status_code == 200
available = resp.json()["personas_available"]
assert "general" in available
assert "coding" in available
assert "creative" in available
def test_setup_status_tolerates_corrupt_progress_file(test_client, setup_config_dir):
"""Corrupt setup-progress.json falls back to step=0 without crashing."""
(setup_config_dir / "setup-progress.json").write_text("not-valid-json{{")
resp = test_client.get("/api/setup/status", headers=test_client.auth_headers)
assert resp.status_code == 200
assert resp.json()["step"] == 0
# ---------------------------------------------------------------------------
# POST /api/setup/persona
# ---------------------------------------------------------------------------
def test_setup_persona_valid(test_client, setup_config_dir):
"""Valid persona ID returns 200 and writes persona.json."""
resp = test_client.post(
"/api/setup/persona",
json={"persona": "general"},
headers=test_client.auth_headers,
)
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["persona"] == "general"
assert "name" in data
# File was written
persona_file = setup_config_dir / "persona.json"
assert persona_file.exists()
stored = json.loads(persona_file.read_text())
assert stored["persona"] == "general"
def test_setup_persona_invalid(test_client, setup_config_dir):
"""Unknown persona ID returns 400."""
resp = test_client.post(
"/api/setup/persona",
json={"persona": "nonexistent"},
headers=test_client.auth_headers,
)
assert resp.status_code == 400
def test_setup_persona_stores_system_prompt(test_client, setup_config_dir):
"""persona.json written by setup_persona contains the system_prompt field."""
test_client.post(
"/api/setup/persona",
json={"persona": "coding"},
headers=test_client.auth_headers,
)
stored = json.loads((setup_config_dir / "persona.json").read_text())
assert "system_prompt" in stored
assert len(stored["system_prompt"]) > 10
def test_setup_persona_updates_progress(test_client, setup_config_dir):
"""Selecting a persona advances the progress step to 2."""
test_client.post(
"/api/setup/persona",
json={"persona": "creative"},
headers=test_client.auth_headers,
)
progress = json.loads((setup_config_dir / "setup-progress.json").read_text())
assert progress["step"] == 2
assert progress["persona_selected"] is True
# ---------------------------------------------------------------------------
# POST /api/setup/complete
# ---------------------------------------------------------------------------
def test_setup_complete_creates_marker(test_client, setup_config_dir):
"""POST /api/setup/complete returns 200 and creates setup-complete.json."""
resp = test_client.post("/api/setup/complete", headers=test_client.auth_headers)
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert (setup_config_dir / "setup-complete.json").exists()
def test_setup_complete_removes_progress_file(test_client, setup_config_dir):
"""POST /api/setup/complete deletes setup-progress.json if it exists."""
(setup_config_dir / "setup-progress.json").write_text(json.dumps({"step": 2}))
test_client.post("/api/setup/complete", headers=test_client.auth_headers)
assert not (setup_config_dir / "setup-progress.json").exists()
def test_setup_complete_idempotent(test_client, setup_config_dir):
"""Calling complete twice does not error."""
test_client.post("/api/setup/complete", headers=test_client.auth_headers)
resp = test_client.post("/api/setup/complete", headers=test_client.auth_headers)
assert resp.status_code == 200
# ---------------------------------------------------------------------------
# GET /api/setup/personas and GET /api/setup/persona/{id}
# ---------------------------------------------------------------------------
def test_list_personas_returns_all(test_client, setup_config_dir):
"""GET /api/setup/personas lists all built-in personas."""
resp = test_client.get("/api/setup/personas", headers=test_client.auth_headers)
assert resp.status_code == 200
ids = {p["id"] for p in resp.json()["personas"]}
assert ids >= {"general", "coding", "creative"}
def test_list_personas_includes_name_and_icon(test_client, setup_config_dir):
"""Each persona entry has name and icon fields."""
resp = test_client.get("/api/setup/personas", headers=test_client.auth_headers)
for persona in resp.json()["personas"]:
assert "name" in persona
assert "icon" in persona
def test_get_persona_info_valid(test_client, setup_config_dir):
"""GET /api/setup/persona/general returns the persona details."""
resp = test_client.get("/api/setup/persona/general", headers=test_client.auth_headers)
assert resp.status_code == 200
data = resp.json()
assert data["id"] == "general"
assert "name" in data
assert "system_prompt" in data
def test_get_persona_info_not_found(test_client, setup_config_dir):
"""GET /api/setup/persona/<unknown> returns 404."""
resp = test_client.get("/api/setup/persona/unknown-persona", headers=test_client.auth_headers)
assert resp.status_code == 404
# ---------------------------------------------------------------------------
# Diagnostics fallback timeout type
# ---------------------------------------------------------------------------
def test_diagnostics_fallback_uses_client_timeout(test_client, monkeypatch):
"""The fallback health-check loop must pass aiohttp.ClientTimeout, not a bare int."""
import aiohttp
import routers.setup as setup_router
# Ensure the diagnostic script does NOT exist so we hit the fallback path
monkeypatch.setattr(setup_router, "INSTALL_DIR", "/nonexistent-path")
# Provide at least one service so the health-check loop body executes
monkeypatch.setattr(setup_router, "SERVICES", {
"fake-svc": {"name": "Fake", "host": "localhost", "port": 1, "health": "/"}
})
captured_timeouts = []
class FakeResponse:
status = 200
async def __aenter__(self):
return self
async def __aexit__(self, *args):
pass
class FakeSession:
def __init__(self, **kwargs):
pass
async def __aenter__(self):
return self
async def __aexit__(self, *args):
pass
def get(self, url, **kwargs):
timeout = kwargs.get("timeout")
captured_timeouts.append(timeout)
return FakeResponse()
monkeypatch.setattr(aiohttp, "ClientSession", FakeSession)
resp = test_client.post("/api/setup/test", headers=test_client.auth_headers)
assert resp.status_code == 200
# Verify at least one request was made and all timeouts are ClientTimeout instances
assert len(captured_timeouts) > 0, "Expected at least one health check request"
for t in captured_timeouts:
assert isinstance(t, aiohttp.ClientTimeout), (
f"Expected aiohttp.ClientTimeout but got {type(t).__name__}: {t}"
)