2025-12-15 23:25:56 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
import uuid
|
|
|
|
|
|
2025-12-15 23:25:56 +08:00
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
|
|
|
|
|
|
|
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
async def _create_dify_database(test_client, admin_headers) -> str:
|
|
|
|
|
response = await test_client.post(
|
|
|
|
|
"/api/knowledge/databases",
|
|
|
|
|
json={
|
|
|
|
|
"database_name": f"pytest_graph_dify_{uuid.uuid4().hex[:8]}",
|
|
|
|
|
"description": "Graph router Dify negative test",
|
|
|
|
|
"kb_type": "dify",
|
|
|
|
|
"additional_params": {
|
|
|
|
|
"dify_api_url": "https://api.dify.ai/v1",
|
|
|
|
|
"dify_token": "test-token",
|
|
|
|
|
"dify_dataset_id": f"dataset-{uuid.uuid4().hex[:8]}",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
headers=admin_headers,
|
|
|
|
|
)
|
|
|
|
|
assert response.status_code == 200, response.text
|
2026-05-21 19:32:36 +08:00
|
|
|
return response.json()["slug"]
|
2026-05-17 13:06:25 +08:00
|
|
|
|
|
|
|
|
|
2026-05-21 19:32:36 +08:00
|
|
|
async def _delete_database(test_client, admin_headers, slug: str) -> None:
|
|
|
|
|
await test_client.delete(f"/api/knowledge/databases/{slug}", headers=admin_headers)
|
2026-05-17 13:06:25 +08:00
|
|
|
|
|
|
|
|
|
2025-12-20 15:13:56 +08:00
|
|
|
async def test_graph_routes_require_auth(test_client):
|
|
|
|
|
response = await test_client.get("/api/graph/list")
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_standard_user_cannot_access_graph_endpoints(test_client, standard_user):
|
|
|
|
|
response = await test_client.get("/api/graph/list", headers=standard_user["headers"])
|
|
|
|
|
assert response.status_code == 403
|
|
|
|
|
|
|
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
async def test_get_graphs_list_only_returns_milvus(test_client, admin_headers, knowledge_database):
|
2025-12-15 23:25:56 +08:00
|
|
|
response = await test_client.get("/api/graph/list", headers=admin_headers)
|
2026-05-17 13:06:25 +08:00
|
|
|
|
2025-12-15 23:25:56 +08:00
|
|
|
assert response.status_code == 200
|
|
|
|
|
payload = response.json()
|
|
|
|
|
assert payload["success"] is True
|
2026-05-17 13:06:25 +08:00
|
|
|
assert isinstance(payload["data"], list)
|
|
|
|
|
assert payload["data"]
|
|
|
|
|
assert all(graph["type"] == "milvus" for graph in payload["data"])
|
2026-05-21 19:32:36 +08:00
|
|
|
assert any(graph["id"] == knowledge_database["slug"] for graph in payload["data"])
|
2025-12-16 10:28:44 +08:00
|
|
|
|
|
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
@pytest.mark.parametrize("path", ["/api/graph/subgraph", "/api/graph/stats", "/api/graph/labels"])
|
|
|
|
|
async def test_graph_endpoints_reject_non_milvus_types(test_client, admin_headers, path):
|
2026-05-21 19:32:36 +08:00
|
|
|
slug = await _create_dify_database(test_client, admin_headers)
|
2026-05-17 13:06:25 +08:00
|
|
|
try:
|
2026-05-21 19:32:36 +08:00
|
|
|
response = await test_client.get(path, params={"slug": slug}, headers=admin_headers)
|
2026-05-17 13:06:25 +08:00
|
|
|
finally:
|
2026-05-21 19:32:36 +08:00
|
|
|
await _delete_database(test_client, admin_headers, slug)
|
2025-12-15 23:25:56 +08:00
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
assert response.status_code == 404
|
|
|
|
|
assert "only supports Milvus" in response.text
|
2025-12-15 23:25:56 +08:00
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
|
|
|
|
|
async def test_milvus_subgraph_endpoint(test_client, admin_headers, knowledge_database):
|
2025-12-15 23:25:56 +08:00
|
|
|
response = await test_client.get(
|
2026-05-17 13:06:25 +08:00
|
|
|
"/api/graph/subgraph",
|
2026-05-21 19:32:36 +08:00
|
|
|
params={"slug": knowledge_database["slug"], "node_label": "*", "max_nodes": 10},
|
2026-05-17 13:06:25 +08:00
|
|
|
headers=admin_headers,
|
2025-12-15 23:25:56 +08:00
|
|
|
)
|
2026-05-17 13:06:25 +08:00
|
|
|
|
|
|
|
|
assert response.status_code == 200, response.text
|
2025-12-15 23:25:56 +08:00
|
|
|
payload = response.json()
|
|
|
|
|
assert payload["success"] is True
|
2026-05-17 13:06:25 +08:00
|
|
|
assert "nodes" in payload["data"]
|
|
|
|
|
assert "edges" in payload["data"]
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
async def test_milvus_stats_endpoint(test_client, admin_headers, knowledge_database):
|
2025-12-15 23:25:56 +08:00
|
|
|
response = await test_client.get(
|
2026-05-17 13:06:25 +08:00
|
|
|
"/api/graph/stats",
|
2026-05-21 19:32:36 +08:00
|
|
|
params={"slug": knowledge_database["slug"]},
|
2026-05-17 13:06:25 +08:00
|
|
|
headers=admin_headers,
|
2025-12-15 23:25:56 +08:00
|
|
|
)
|
|
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
assert response.status_code == 200, response.text
|
2025-12-15 23:25:56 +08:00
|
|
|
payload = response.json()
|
|
|
|
|
assert payload["success"] is True
|
2026-05-17 13:06:25 +08:00
|
|
|
assert "total_nodes" in payload["data"]
|
|
|
|
|
assert "total_edges" in payload["data"]
|
|
|
|
|
assert "entity_types" in payload["data"]
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
async def test_milvus_labels_endpoint(test_client, admin_headers, knowledge_database):
|
2025-12-15 23:25:56 +08:00
|
|
|
response = await test_client.get(
|
2026-05-17 13:06:25 +08:00
|
|
|
"/api/graph/labels",
|
2026-05-21 19:32:36 +08:00
|
|
|
params={"slug": knowledge_database["slug"]},
|
2026-05-17 13:06:25 +08:00
|
|
|
headers=admin_headers,
|
2025-12-15 23:25:56 +08:00
|
|
|
)
|
2026-05-17 13:06:25 +08:00
|
|
|
|
|
|
|
|
assert response.status_code == 200, response.text
|
2025-12-15 23:25:56 +08:00
|
|
|
payload = response.json()
|
|
|
|
|
assert payload["success"] is True
|
2026-05-17 13:06:25 +08:00
|
|
|
assert "labels" in payload["data"]
|
|
|
|
|
assert isinstance(payload["data"]["labels"], list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"path",
|
|
|
|
|
[
|
|
|
|
|
"/api/graph/neo4j/nodes",
|
|
|
|
|
"/api/graph/neo4j/node",
|
|
|
|
|
"/api/graph/neo4j/info",
|
|
|
|
|
"/api/graph/neo4j/index-entities",
|
|
|
|
|
"/api/graph/neo4j/add-entities",
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
async def test_neo4j_upload_routes_are_removed(test_client, admin_headers, path):
|
|
|
|
|
method = test_client.post if path.endswith(("index-entities", "add-entities")) else test_client.get
|
|
|
|
|
response = await method(path, headers=admin_headers)
|
|
|
|
|
assert response.status_code == 404
|