| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- import json
- from fastapi.testclient import TestClient
- from ontorefactor_governance import api
- from ontorefactor_governance.db import Database
- def test_standalone_api_and_web_console(monkeypatch):
- db = Database("sqlite:///:memory:")
- monkeypatch.setattr(api, "get_database", lambda: db)
- client = TestClient(api.app)
- headers = {"X-Tenant-ID": "api-test"}
- assert client.get("/api/health").json()["ok"] is True
- page = client.get("/")
- assert page.status_code == 200
- assert "OntoRefactor" in page.text
- seeded = client.post("/api/v1/governance/bootstrap/tenant-isolation", headers=headers)
- assert seeded.status_code == 200
- project_id = seeded.json()["project"]["id"]
- projects = client.get("/api/v1/governance/projects", headers=headers).json()["projects"]
- assert len(projects) == 1
- assert projects[0]["id"] == project_id
- overview = client.get(f"/api/v1/governance/projects/{project_id}/overview", headers=headers).json()
- assert overview["totals"]["elements"] == 48
- assert overview["totals"]["open_issues"] == 0
- exported = client.get(f"/api/v1/governance/projects/{project_id}/export.jsonld", headers=headers)
- assert exported.status_code == 200
- assert exported.json()["@graph"]
- other_tenant = client.get(f"/api/v1/governance/projects/{project_id}/overview", headers={"X-Tenant-ID": "other"})
- assert other_tenant.status_code == 404
- db.close()
- def test_agent_endpoint(monkeypatch):
- db = Database("sqlite:///:memory:")
- monkeypatch.setattr(api, "get_database", lambda: db)
- client = TestClient(api.app)
- headers = {"X-Tenant-ID": "agent-api"}
- project_id = client.post("/api/v1/governance/projects", headers=headers, json={
- "name": "Imported governance", "namespace": "urn:api:imported"
- }).json()["id"]
- response = client.post(f"/api/v1/governance/projects/{project_id}/agent-runs/analyze", headers=headers, json={
- "source_type": "ddl",
- "source_name": "orders.sql",
- "environment": "development",
- "content": "CREATE TABLE orders (id INTEGER PRIMARY KEY, tenant_id INTEGER NOT NULL);",
- })
- assert response.status_code == 200
- assert response.json()["persisted"]["elements"] == 7
- runs = client.get(f"/api/v1/governance/projects/{project_id}/agent-runs", headers=headers).json()["runs"]
- assert len(runs) == 1
- assert runs[0]["status"] == "completed"
- db.close()
- def test_source_file_preview_and_upload_agent_endpoint(monkeypatch):
- db = Database("sqlite:///:memory:")
- monkeypatch.setattr(api, "get_database", lambda: db)
- client = TestClient(api.app)
- headers = {"X-Tenant-ID": "upload-api"}
- project_id = client.post("/api/v1/governance/projects", headers=headers, json={
- "name": "Uploaded governance", "namespace": "urn:api:uploaded"
- }).json()["id"]
- ddl = b"CREATE TABLE iam.customer (id BIGINT PRIMARY KEY, mobile VARCHAR(32));"
- preview = client.post(
- f"/api/v1/governance/projects/{project_id}/sources/preview",
- headers=headers,
- files={"file": ("customer.sql", ddl, "text/plain")},
- data={"source_type": "auto"},
- )
- assert preview.status_code == 200
- assert preview.json()["source_type"] == "ddl"
- assert preview.json()["detected"] == {
- "tables": 1, "columns": 2, "apis": 0, "schemas": 0, "sensitive_fields": ["mobile"]
- }
- assert preview.json()["content"].startswith("CREATE TABLE")
- analyzed = client.post(
- f"/api/v1/governance/projects/{project_id}/agent-runs/upload",
- headers=headers,
- files={"file": ("customer.sql", ddl, "text/plain")},
- data={"source_type": "auto", "semantic_mode": "deterministic", "instruction": "识别敏感字段"},
- )
- assert analyzed.status_code == 200
- assert analyzed.json()["status"] == "completed"
- assert analyzed.json()["persisted"]["elements"] > 0
- evidence = client.get(
- f"/api/v1/governance/projects/{project_id}/evidence", headers=headers
- ).json()["evidence"]
- source = next(item for item in evidence if item["kind"] == "SourceArtifact")
- assert source["content"].startswith("CREATE TABLE")
- assert source["metadata"]["upload"]["name"] == "customer.sql"
- db.close()
- def test_llm_status_is_safe_and_forced_mode_requires_key(monkeypatch):
- monkeypatch.delenv("LONGCAT_API_KEY", raising=False)
- monkeypatch.setenv("ONTOREFACTOR_LLM_MODE", "auto")
- db = Database("sqlite:///:memory:")
- monkeypatch.setattr(api, "get_database", lambda: db)
- client = TestClient(api.app)
- headers = {"X-Tenant-ID": "llm-api"}
- status = client.get("/api/v1/governance/llm/status", headers=headers)
- assert status.status_code == 200
- assert status.json()["configured"] is False
- assert "api_key" not in json.dumps(status.json()).lower()
- project_id = client.post("/api/v1/governance/projects", headers=headers, json={
- "name": "LLM project", "namespace": "urn:api:llm"
- }).json()["id"]
- response = client.post(f"/api/v1/governance/projects/{project_id}/agent-runs/analyze", headers=headers, json={
- "source_type": "ddl", "semantic_mode": "llm",
- "content": "CREATE TABLE customer (id INTEGER PRIMARY KEY);",
- })
- assert response.status_code == 503
- assert response.json()["detail"]["code"] == "LLM_NOT_CONFIGURED"
- db.close()
- def test_llm_connection_failure_is_a_structured_api_error(monkeypatch):
- def fail_connection():
- from ontorefactor_governance.service import GovernanceError
- raise GovernanceError("LongCat 鉴权失败,请检查 LONGCAT_API_KEY", code="LLM_AUTH_FAILED", status=502)
- monkeypatch.setattr(api, "test_llm_connection", fail_connection)
- client = TestClient(api.app)
- response = client.post("/api/v1/governance/llm/test", headers={"X-Tenant-ID": "llm-api"})
- assert response.status_code == 502
- assert response.json()["detail"]["code"] == "LLM_AUTH_FAILED"
- def test_challenge_cup_demo_bootstrap_endpoint(monkeypatch):
- db = Database("sqlite:///:memory:")
- monkeypatch.setattr(api, "get_database", lambda: db)
- client = TestClient(api.app)
- headers = {"X-Tenant-ID": "challenge-demo"}
- response = client.post("/api/v1/governance/bootstrap/challenge-cup", headers=headers)
- assert response.status_code == 200
- payload = response.json()
- assert payload["overview"]["demo"]["decision"] == "暂缓删除"
- assert payload["overview"]["totals"]["open_issues"] == 4
- db.close()
|