Files
resolutionflow/backend/tests/test_admin_gallery.py
chihlasm 12a373a2a2 feat(gallery): add admin gallery curation endpoints and management page (Task 6)
Add super-admin-only backend endpoints for toggling is_gallery_featured and
gallery_sort_order on flows and scripts, plus a frontend GalleryManagementPage
with toggle switches, editable sort order fields, and name/featured filters.
13 integration tests; all pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 20:04:40 +00:00

313 lines
10 KiB
Python

"""Integration tests for admin gallery curation endpoints."""
import uuid
import pytest
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.tree import Tree
from app.models.script_template import ScriptTemplate, ScriptCategory
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
async def _create_tree(db: AsyncSession, admin_user_id: str) -> Tree:
"""Insert a minimal Tree directly into the DB."""
tree = Tree(
id=uuid.uuid4(),
name="Gallery Test Flow",
tree_type="troubleshooting",
visibility="public",
is_gallery_featured=False,
gallery_sort_order=0,
tree_structure={
"id": "root",
"type": "decision",
"question": "Test?",
"options": [],
"children": [],
},
author_id=uuid.UUID(admin_user_id),
)
db.add(tree)
await db.commit()
await db.refresh(tree)
return tree
async def _create_script(db: AsyncSession, admin_user_id: str) -> ScriptTemplate:
"""Insert a minimal ScriptTemplate directly into the DB."""
# Need a category first
category = ScriptCategory(
id=uuid.uuid4(),
name="Test Category",
slug=f"test-category-{uuid.uuid4().hex[:6]}",
)
db.add(category)
await db.flush()
script = ScriptTemplate(
id=uuid.uuid4(),
category_id=category.id,
name="Gallery Test Script",
slug=f"gallery-test-script-{uuid.uuid4().hex[:6]}",
script_body="Write-Host 'Test'",
is_gallery_featured=False,
gallery_sort_order=0,
created_by=uuid.UUID(admin_user_id) if admin_user_id else None,
)
db.add(script)
await db.commit()
await db.refresh(script)
return script
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
class TestAdminGallery:
"""Test suite for admin gallery curation endpoints."""
# -- Auth guard tests --
@pytest.mark.asyncio
async def test_featured_list_requires_admin(
self, client: AsyncClient, auth_headers: dict
):
"""Non-admin users get 403 on featured list."""
response = await client.get("/api/v1/admin/gallery/featured", headers=auth_headers)
assert response.status_code == 403
@pytest.mark.asyncio
async def test_feature_toggle_flow_requires_admin(
self, client: AsyncClient, auth_headers: dict
):
"""Non-admin users get 403 when trying to toggle flow feature."""
fake_id = str(uuid.uuid4())
response = await client.patch(
f"/api/v1/admin/gallery/flows/{fake_id}/feature",
json={"is_gallery_featured": True},
headers=auth_headers,
)
assert response.status_code == 403
@pytest.mark.asyncio
async def test_feature_toggle_script_requires_admin(
self, client: AsyncClient, auth_headers: dict
):
"""Non-admin users get 403 when trying to toggle script feature."""
fake_id = str(uuid.uuid4())
response = await client.patch(
f"/api/v1/admin/gallery/scripts/{fake_id}/feature",
json={"is_gallery_featured": True},
headers=auth_headers,
)
assert response.status_code == 403
# -- Feature toggle tests --
@pytest.mark.asyncio
async def test_toggle_flow_featured_on(
self,
client: AsyncClient,
admin_auth_headers: dict,
test_admin: dict,
test_db: AsyncSession,
):
"""Admin can feature a flow."""
tree = await _create_tree(test_db, test_admin["user_data"]["id"])
response = await client.patch(
f"/api/v1/admin/gallery/flows/{tree.id}/feature",
json={"is_gallery_featured": True},
headers=admin_auth_headers,
)
assert response.status_code == 200
data = response.json()
assert data["is_gallery_featured"] is True
assert data["id"] == str(tree.id)
@pytest.mark.asyncio
async def test_toggle_flow_featured_off(
self,
client: AsyncClient,
admin_auth_headers: dict,
test_admin: dict,
test_db: AsyncSession,
):
"""Admin can un-feature a previously featured flow."""
tree = await _create_tree(test_db, test_admin["user_data"]["id"])
# Feature it first
tree.is_gallery_featured = True
await test_db.commit()
response = await client.patch(
f"/api/v1/admin/gallery/flows/{tree.id}/feature",
json={"is_gallery_featured": False},
headers=admin_auth_headers,
)
assert response.status_code == 200
assert response.json()["is_gallery_featured"] is False
@pytest.mark.asyncio
async def test_toggle_flow_not_found(
self, client: AsyncClient, admin_auth_headers: dict
):
"""Returns 404 when flow ID doesn't exist."""
fake_id = str(uuid.uuid4())
response = await client.patch(
f"/api/v1/admin/gallery/flows/{fake_id}/feature",
json={"is_gallery_featured": True},
headers=admin_auth_headers,
)
assert response.status_code == 404
@pytest.mark.asyncio
async def test_toggle_script_featured_on(
self,
client: AsyncClient,
admin_auth_headers: dict,
test_admin: dict,
test_db: AsyncSession,
):
"""Admin can feature a script."""
script = await _create_script(test_db, test_admin["user_data"]["id"])
response = await client.patch(
f"/api/v1/admin/gallery/scripts/{script.id}/feature",
json={"is_gallery_featured": True},
headers=admin_auth_headers,
)
assert response.status_code == 200
data = response.json()
assert data["is_gallery_featured"] is True
assert data["id"] == str(script.id)
@pytest.mark.asyncio
async def test_toggle_script_not_found(
self, client: AsyncClient, admin_auth_headers: dict
):
"""Returns 404 when script ID doesn't exist."""
fake_id = str(uuid.uuid4())
response = await client.patch(
f"/api/v1/admin/gallery/scripts/{fake_id}/feature",
json={"is_gallery_featured": True},
headers=admin_auth_headers,
)
assert response.status_code == 404
# -- Sort order tests --
@pytest.mark.asyncio
async def test_update_flow_sort_order(
self,
client: AsyncClient,
admin_auth_headers: dict,
test_admin: dict,
test_db: AsyncSession,
):
"""Admin can update gallery sort order for a flow."""
tree = await _create_tree(test_db, test_admin["user_data"]["id"])
response = await client.patch(
f"/api/v1/admin/gallery/flows/{tree.id}/sort-order",
json={"gallery_sort_order": 42},
headers=admin_auth_headers,
)
assert response.status_code == 200
assert response.json()["gallery_sort_order"] == 42
@pytest.mark.asyncio
async def test_update_script_sort_order(
self,
client: AsyncClient,
admin_auth_headers: dict,
test_admin: dict,
test_db: AsyncSession,
):
"""Admin can update gallery sort order for a script."""
script = await _create_script(test_db, test_admin["user_data"]["id"])
response = await client.patch(
f"/api/v1/admin/gallery/scripts/{script.id}/sort-order",
json={"gallery_sort_order": 7},
headers=admin_auth_headers,
)
assert response.status_code == 200
assert response.json()["gallery_sort_order"] == 7
# -- Featured list tests --
@pytest.mark.asyncio
async def test_featured_list_returns_only_featured(
self,
client: AsyncClient,
admin_auth_headers: dict,
test_admin: dict,
test_db: AsyncSession,
):
"""GET /featured returns only items where is_gallery_featured=True."""
# Create two flows: one featured, one not
featured_tree = await _create_tree(test_db, test_admin["user_data"]["id"])
_unfeatured_tree = await _create_tree(test_db, test_admin["user_data"]["id"])
_unfeatured_tree.name = "Unfeatured Flow"
# Feature only the first
featured_tree.is_gallery_featured = True
await test_db.commit()
# Create two scripts: one featured, one not
featured_script = await _create_script(test_db, test_admin["user_data"]["id"])
_unfeatured_script = await _create_script(test_db, test_admin["user_data"]["id"])
featured_script.is_gallery_featured = True
await test_db.commit()
response = await client.get(
"/api/v1/admin/gallery/featured", headers=admin_auth_headers
)
assert response.status_code == 200
data = response.json()
flow_ids = [f["id"] for f in data["flows"]]
script_ids = [s["id"] for s in data["scripts"]]
assert str(featured_tree.id) in flow_ids
assert str(_unfeatured_tree.id) not in flow_ids
assert str(featured_script.id) in script_ids
assert str(_unfeatured_script.id) not in script_ids
@pytest.mark.asyncio
async def test_items_list_requires_admin(
self, client: AsyncClient, auth_headers: dict
):
"""Non-admin users get 403 on all items list."""
response = await client.get("/api/v1/admin/gallery/items", headers=auth_headers)
assert response.status_code == 403
@pytest.mark.asyncio
async def test_items_list_returns_all(
self,
client: AsyncClient,
admin_auth_headers: dict,
test_admin: dict,
test_db: AsyncSession,
):
"""GET /items returns all flows and scripts regardless of featured status."""
tree = await _create_tree(test_db, test_admin["user_data"]["id"])
response = await client.get(
"/api/v1/admin/gallery/items", headers=admin_auth_headers
)
assert response.status_code == 200
data = response.json()
assert "flows" in data
assert "scripts" in data
flow_ids = [f["id"] for f in data["flows"]]
assert str(tree.id) in flow_ids