test_api.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import json
  2. from fastapi.testclient import TestClient
  3. from ontorefactor_governance import api
  4. from ontorefactor_governance.db import Database
  5. def test_standalone_api_and_web_console(monkeypatch):
  6. db = Database("sqlite:///:memory:")
  7. monkeypatch.setattr(api, "get_database", lambda: db)
  8. client = TestClient(api.app)
  9. headers = {"X-Tenant-ID": "api-test"}
  10. assert client.get("/api/health").json()["ok"] is True
  11. page = client.get("/")
  12. assert page.status_code == 200
  13. assert "OntoRefactor" in page.text
  14. seeded = client.post("/api/v1/governance/bootstrap/tenant-isolation", headers=headers)
  15. assert seeded.status_code == 200
  16. project_id = seeded.json()["project"]["id"]
  17. projects = client.get("/api/v1/governance/projects", headers=headers).json()["projects"]
  18. assert len(projects) == 1
  19. assert projects[0]["id"] == project_id
  20. overview = client.get(f"/api/v1/governance/projects/{project_id}/overview", headers=headers).json()
  21. assert overview["totals"]["elements"] == 48
  22. assert overview["totals"]["open_issues"] == 0
  23. exported = client.get(f"/api/v1/governance/projects/{project_id}/export.jsonld", headers=headers)
  24. assert exported.status_code == 200
  25. assert exported.json()["@graph"]
  26. other_tenant = client.get(f"/api/v1/governance/projects/{project_id}/overview", headers={"X-Tenant-ID": "other"})
  27. assert other_tenant.status_code == 404
  28. db.close()
  29. def test_agent_endpoint(monkeypatch):
  30. db = Database("sqlite:///:memory:")
  31. monkeypatch.setattr(api, "get_database", lambda: db)
  32. client = TestClient(api.app)
  33. headers = {"X-Tenant-ID": "agent-api"}
  34. project_id = client.post("/api/v1/governance/projects", headers=headers, json={
  35. "name": "Imported governance", "namespace": "urn:api:imported"
  36. }).json()["id"]
  37. response = client.post(f"/api/v1/governance/projects/{project_id}/agent-runs/analyze", headers=headers, json={
  38. "source_type": "ddl",
  39. "source_name": "orders.sql",
  40. "environment": "development",
  41. "content": "CREATE TABLE orders (id INTEGER PRIMARY KEY, tenant_id INTEGER NOT NULL);",
  42. })
  43. assert response.status_code == 200
  44. assert response.json()["persisted"]["elements"] == 7
  45. runs = client.get(f"/api/v1/governance/projects/{project_id}/agent-runs", headers=headers).json()["runs"]
  46. assert len(runs) == 1
  47. assert runs[0]["status"] == "completed"
  48. db.close()
  49. def test_source_file_preview_and_upload_agent_endpoint(monkeypatch):
  50. db = Database("sqlite:///:memory:")
  51. monkeypatch.setattr(api, "get_database", lambda: db)
  52. client = TestClient(api.app)
  53. headers = {"X-Tenant-ID": "upload-api"}
  54. project_id = client.post("/api/v1/governance/projects", headers=headers, json={
  55. "name": "Uploaded governance", "namespace": "urn:api:uploaded"
  56. }).json()["id"]
  57. ddl = b"CREATE TABLE iam.customer (id BIGINT PRIMARY KEY, mobile VARCHAR(32));"
  58. preview = client.post(
  59. f"/api/v1/governance/projects/{project_id}/sources/preview",
  60. headers=headers,
  61. files={"file": ("customer.sql", ddl, "text/plain")},
  62. data={"source_type": "auto"},
  63. )
  64. assert preview.status_code == 200
  65. assert preview.json()["source_type"] == "ddl"
  66. assert preview.json()["detected"] == {
  67. "tables": 1, "columns": 2, "apis": 0, "schemas": 0, "sensitive_fields": ["mobile"]
  68. }
  69. assert preview.json()["content"].startswith("CREATE TABLE")
  70. analyzed = client.post(
  71. f"/api/v1/governance/projects/{project_id}/agent-runs/upload",
  72. headers=headers,
  73. files={"file": ("customer.sql", ddl, "text/plain")},
  74. data={"source_type": "auto", "semantic_mode": "deterministic", "instruction": "识别敏感字段"},
  75. )
  76. assert analyzed.status_code == 200
  77. assert analyzed.json()["status"] == "completed"
  78. assert analyzed.json()["persisted"]["elements"] > 0
  79. evidence = client.get(
  80. f"/api/v1/governance/projects/{project_id}/evidence", headers=headers
  81. ).json()["evidence"]
  82. source = next(item for item in evidence if item["kind"] == "SourceArtifact")
  83. assert source["content"].startswith("CREATE TABLE")
  84. assert source["metadata"]["upload"]["name"] == "customer.sql"
  85. db.close()
  86. def test_llm_status_is_safe_and_forced_mode_requires_key(monkeypatch):
  87. monkeypatch.delenv("LONGCAT_API_KEY", raising=False)
  88. monkeypatch.setenv("ONTOREFACTOR_LLM_MODE", "auto")
  89. db = Database("sqlite:///:memory:")
  90. monkeypatch.setattr(api, "get_database", lambda: db)
  91. client = TestClient(api.app)
  92. headers = {"X-Tenant-ID": "llm-api"}
  93. status = client.get("/api/v1/governance/llm/status", headers=headers)
  94. assert status.status_code == 200
  95. assert status.json()["configured"] is False
  96. assert "api_key" not in json.dumps(status.json()).lower()
  97. project_id = client.post("/api/v1/governance/projects", headers=headers, json={
  98. "name": "LLM project", "namespace": "urn:api:llm"
  99. }).json()["id"]
  100. response = client.post(f"/api/v1/governance/projects/{project_id}/agent-runs/analyze", headers=headers, json={
  101. "source_type": "ddl", "semantic_mode": "llm",
  102. "content": "CREATE TABLE customer (id INTEGER PRIMARY KEY);",
  103. })
  104. assert response.status_code == 503
  105. assert response.json()["detail"]["code"] == "LLM_NOT_CONFIGURED"
  106. db.close()
  107. def test_llm_connection_failure_is_a_structured_api_error(monkeypatch):
  108. def fail_connection():
  109. from ontorefactor_governance.service import GovernanceError
  110. raise GovernanceError("LongCat 鉴权失败,请检查 LONGCAT_API_KEY", code="LLM_AUTH_FAILED", status=502)
  111. monkeypatch.setattr(api, "test_llm_connection", fail_connection)
  112. client = TestClient(api.app)
  113. response = client.post("/api/v1/governance/llm/test", headers={"X-Tenant-ID": "llm-api"})
  114. assert response.status_code == 502
  115. assert response.json()["detail"]["code"] == "LLM_AUTH_FAILED"
  116. def test_challenge_cup_demo_bootstrap_endpoint(monkeypatch):
  117. db = Database("sqlite:///:memory:")
  118. monkeypatch.setattr(api, "get_database", lambda: db)
  119. client = TestClient(api.app)
  120. headers = {"X-Tenant-ID": "challenge-demo"}
  121. response = client.post("/api/v1/governance/bootstrap/challenge-cup", headers=headers)
  122. assert response.status_code == 200
  123. payload = response.json()
  124. assert payload["overview"]["demo"]["decision"] == "暂缓删除"
  125. assert payload["overview"]["totals"]["open_issues"] == 4
  126. db.close()