Files
resolutionflow/backend/tests/test_trees.py
chihlasm 02d06acfb8 feat: add super admin bypass in tree list filter
Super admins now see all trees regardless of ownership, team, or
public/default status. Previously the build_tree_access_filter function
had no super_admin check, so admins could only see their own trees plus
public/default/team trees.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:25:56 -05:00

217 lines
7.3 KiB
Python

"""Integration tests for tree endpoints."""
import pytest
from httpx import AsyncClient
class TestTrees:
"""Test suite for decision tree endpoints."""
@pytest.mark.asyncio
async def test_create_tree(self, client: AsyncClient, auth_headers: dict):
"""Test creating a new decision tree."""
tree_data = {
"name": "Network Troubleshooting",
"description": "Troubleshoot network connectivity issues",
"category": "Networking",
"tree_structure": {
"id": "root",
"type": "decision",
"question": "Can you ping the gateway?",
"options": [
{"id": "yes", "label": "Yes", "next_node_id": "check_dns"},
{"id": "no", "label": "No", "next_node_id": "check_cable"}
],
"children": [
{
"id": "check_dns",
"type": "decision",
"question": "Can you resolve DNS?",
"options": [],
"children": []
},
{
"id": "check_cable",
"type": "solution",
"title": "Check Network Cable",
"description": "Verify the network cable is connected"
}
]
}
}
response = await client.post(
"/api/v1/trees",
json=tree_data,
headers=auth_headers
)
assert response.status_code == 201
data = response.json()
assert data["name"] == tree_data["name"]
assert data["category"] == tree_data["category"]
assert data["is_active"] is True
assert data["version"] == 1
assert "id" in data
@pytest.mark.asyncio
async def test_list_trees(
self, client: AsyncClient, auth_headers: dict, test_tree: dict
):
"""Test listing decision trees."""
response = await client.get("/api/v1/trees", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) >= 1
# Check that our test tree is in the list
tree_ids = [tree["id"] for tree in data]
assert test_tree["id"] in tree_ids
@pytest.mark.asyncio
async def test_get_tree_by_id(
self, client: AsyncClient, auth_headers: dict, test_tree: dict
):
"""Test getting a specific tree by ID."""
response = await client.get(
f"/api/v1/trees/{test_tree['id']}",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert data["id"] == test_tree["id"]
assert data["name"] == test_tree["name"]
assert "tree_structure" in data
@pytest.mark.asyncio
async def test_get_nonexistent_tree(
self, client: AsyncClient, auth_headers: dict
):
"""Test getting a tree that doesn't exist."""
fake_id = "00000000-0000-0000-0000-000000000000"
response = await client.get(
f"/api/v1/trees/{fake_id}",
headers=auth_headers
)
assert response.status_code == 404
@pytest.mark.asyncio
async def test_search_trees(
self, client: AsyncClient, auth_headers: dict, test_tree: dict
):
"""Test full-text search for trees."""
response = await client.get(
"/api/v1/trees/search?q=test",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
# Should find our test tree
if len(data) > 0:
assert any(tree["id"] == test_tree["id"] for tree in data)
@pytest.mark.asyncio
async def test_get_categories(
self, client: AsyncClient, auth_headers: dict, test_tree: dict
):
"""Test getting unique tree categories."""
response = await client.get(
"/api/v1/trees/categories",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
# Should include our test tree's category
assert test_tree["category"] in data
@pytest.mark.asyncio
async def test_update_tree(
self, client: AsyncClient, auth_headers: dict, test_tree: dict
):
"""Test updating a tree."""
update_data = {
"name": "Updated Tree Name",
"description": "Updated description"
}
response = await client.put(
f"/api/v1/trees/{test_tree['id']}",
json=update_data,
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert data["name"] == update_data["name"]
assert data["description"] == update_data["description"]
# Version only increments when tree_structure is updated
assert data["version"] == 1
@pytest.mark.asyncio
async def test_delete_tree(
self, client: AsyncClient, admin_auth_headers: dict, test_tree: dict
):
"""Test soft-deleting a tree (admin only)."""
response = await client.delete(
f"/api/v1/trees/{test_tree['id']}",
headers=admin_auth_headers
)
assert response.status_code == 204
# Verify tree is no longer in active list
list_response = await client.get("/api/v1/trees", headers=admin_auth_headers)
active_trees = list_response.json()
active_ids = [tree["id"] for tree in active_trees]
assert test_tree["id"] not in active_ids
@pytest.mark.asyncio
async def test_super_admin_sees_all_trees(
self, client: AsyncClient, auth_headers: dict, admin_auth_headers: dict
):
"""Test that super admin can see all trees including private ones from other users."""
# Create a private (non-public, non-default) tree as a regular user
tree_data = {
"name": "Private User Tree",
"description": "Only visible to author and super admin",
"tree_structure": {
"id": "root",
"type": "solution",
"title": "Private",
"description": "Private tree"
},
"is_public": False,
"is_default": False
}
create_response = await client.post(
"/api/v1/trees", json=tree_data, headers=auth_headers
)
assert create_response.status_code == 201
private_tree_id = create_response.json()["id"]
# Super admin should see it in list
list_response = await client.get("/api/v1/trees", headers=admin_auth_headers)
assert list_response.status_code == 200
tree_ids = [t["id"] for t in list_response.json()]
assert private_tree_id in tree_ids
@pytest.mark.asyncio
async def test_create_tree_unauthorized(self, client: AsyncClient):
"""Test that creating a tree without auth fails."""
tree_data = {
"name": "Unauthorized Tree",
"tree_structure": {"id": "root", "type": "decision"}
}
response = await client.post("/api/v1/trees", json=tree_data)
assert response.status_code == 401