"""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