"""API tests for discovery browse & search endpoints. docs/planning/discovery.md (task 300da1dcddf3). """ import pytest from fastapi.testclient import TestClient from app.main import app def _register(client: TestClient, username: str) -> tuple[int, dict[str, str]]: resp = client.post( "/api/auth/register", json={ "username": username, "email": f"{username}@example.com", "password": "secret123", "date_of_birth": "1990-01-01", }, ) assert resp.status_code == 201, resp.text token = resp.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} me = client.get("/api/users/me", headers=headers) assert me.status_code == 200, me.text return me.json()["id"], headers @pytest.mark.asyncio async def test_discovery_public_worlds_only_and_fields(): client = TestClient(app) uid, headers = _register(client, "creator_browse_1") # Create public world pub_resp = client.post( "/api/worlds/", headers=headers, json={ "name": "Fantasy Kingdom", "description": "A magical realm", "visibility": "public", "category": "RPG", "tags": ["magic", "dragons"], }, ) assert pub_resp.status_code == 201, pub_resp.text pub_id = pub_resp.json()["id"] # Create private world priv_resp = client.post( "/api/worlds/", headers=headers, json={ "name": "Secret Base", "description": "Classified", "visibility": "private", "category": "RPG", "tags": ["secret"], }, ) assert priv_resp.status_code == 201, priv_resp.text # 1. Anonymous listing: only public world is returned anon_resp = client.get("/api/discovery/worlds") assert anon_resp.status_code == 200, anon_resp.text data = anon_resp.json() assert data["total"] >= 1 items = data["items"] ids = [w["id"] for w in items] assert pub_id in ids assert priv_resp.json()["id"] not in ids # Check fields of returned item pub_item = next(w for w in items if w["id"] == pub_id) assert pub_item["name"] == "Fantasy Kingdom" assert pub_item["description"] == "A magical realm" assert pub_item["category"] == "RPG" assert "magic" in pub_item["tags"] assert pub_item["play_count"] == 0 assert pub_item["owner"]["id"] == uid assert pub_item["owner"]["username"] == "creator_browse_1" # 2. Authenticated caller also does NOT see private worlds in discovery auth_resp = client.get("/api/discovery/worlds", headers=headers) assert auth_resp.status_code == 200 auth_ids = [w["id"] for w in auth_resp.json()["items"]] assert priv_resp.json()["id"] not in auth_ids @pytest.mark.asyncio async def test_discovery_filtering_and_categories(): client = TestClient(app) uid, headers = _register(client, "creator_filter_2") # Create distinct worlds client.post( "/api/worlds/", headers=headers, json={"name": "Cyber Arena", "visibility": "public", "category": "Action", "tags": ["pvp", "fps"]}, ) client.post( "/api/worlds/", headers=headers, json={"name": "Peaceful Meadow", "visibility": "public", "category": "Social", "tags": ["relax", "hangout"]}, ) client.post( "/api/worlds/", headers=headers, json={"name": "Cyber Hangout", "visibility": "public", "category": "Social", "tags": ["cyberpunk", "chat"]}, ) # Search by q res_q = client.get("/api/discovery/worlds?q=cyber") assert res_q.status_code == 200 names = [w["name"] for w in res_q.json()["items"]] assert "Cyber Arena" in names assert "Cyber Hangout" in names assert "Peaceful Meadow" not in names # Filter by category (case-insensitive) res_cat = client.get("/api/discovery/worlds?category=action") assert res_cat.status_code == 200 names = [w["name"] for w in res_cat.json()["items"]] assert "Cyber Arena" in names assert "Peaceful Meadow" not in names # Filter by tags (overlap) res_tags = client.get("/api/discovery/worlds?tags=relax,pvp") assert res_tags.status_code == 200 names = [w["name"] for w in res_tags.json()["items"]] assert "Peaceful Meadow" in names assert "Cyber Arena" in names assert "Cyber Hangout" not in names # Get distinct categories res_cats = client.get("/api/discovery/categories") assert res_cats.status_code == 200 cats = res_cats.json() assert "Action" in cats assert "Social" in cats @pytest.mark.asyncio async def test_discovery_sorting_and_pagination(): client = TestClient(app) uid, headers = _register(client, "creator_sort_3") w1 = client.post( "/api/worlds/", headers=headers, json={"name": "Sort World 1", "visibility": "public"}, ).json() w2 = client.post( "/api/worlds/", headers=headers, json={"name": "Sort World 2", "visibility": "public"}, ).json() # Record visits on w1 so play_count = 1 v_res = client.post(f"/api/discovery/worlds/{w1['id']}/visit") assert v_res.status_code == 200 # Sort by most_played res_mp = client.get("/api/discovery/worlds?sort=most_played") assert res_mp.status_code == 200 items = res_mp.json()["items"] # w1 should come before w2 idx_w1 = next(i for i, w in enumerate(items) if w["id"] == w1["id"]) idx_w2 = next(i for i, w in enumerate(items) if w["id"] == w2["id"]) assert idx_w1 < idx_w2 # Sort by newest (default) res_new = client.get("/api/discovery/worlds?sort=newest") assert res_new.status_code == 200 items_new = res_new.json()["items"] idx_w1_n = next(i for i, w in enumerate(items_new) if w["id"] == w1["id"]) idx_w2_n = next(i for i, w in enumerate(items_new) if w["id"] == w2["id"]) # w2 was created after w1, so w2 is newer assert idx_w2_n < idx_w1_n # Pagination: limit 1, skip 0 p1 = client.get("/api/discovery/worlds?limit=1&skip=0") assert p1.status_code == 200 assert len(p1.json()["items"]) == 1 assert p1.json()["total"] >= 2 assert p1.json()["skip"] == 0 assert p1.json()["limit"] == 1