"""Tests for category CRUD endpoints.""" import pytest from httpx import AsyncClient from tests.conftest import create_user_and_login async def _get_first_category(client: AsyncClient, token: str) -> dict: resp = await client.get( "/api/v1/categories", headers={"Authorization": f"Bearer {token}"}, ) return resp.json()[0] @pytest.mark.asyncio async def test_list_categories_after_register(client: AsyncClient): _, token = await create_user_and_login(client) resp = await client.get( "/api/v1/categories", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 cats = resp.json() assert len(cats) == 9 # 6 expense + 3 income defaults for c in cats: assert c["is_default"] is True @pytest.mark.asyncio async def test_create_category(client: AsyncClient): _, token = await create_user_and_login(client) resp = await client.post( "/api/v1/categories", json={"name": "Épargne", "type": "expense", "color": "#123456", "icon": "piggy-bank"}, headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 201 data = resp.json() assert data["name"] == "Épargne" assert data["color"] == "#123456" assert data["is_default"] is False @pytest.mark.asyncio async def test_update_category(client: AsyncClient): _, token = await create_user_and_login(client) create_resp = await client.post( "/api/v1/categories", json={"name": "Old Name", "type": "income"}, headers={"Authorization": f"Bearer {token}"}, ) cat_id = create_resp.json()["id"] resp = await client.put( f"/api/v1/categories/{cat_id}", json={"name": "New Name", "color": "#aabbcc"}, headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 assert resp.json()["name"] == "New Name" assert resp.json()["color"] == "#aabbcc" @pytest.mark.asyncio async def test_delete_category_no_transactions(client: AsyncClient): _, token = await create_user_and_login(client) create_resp = await client.post( "/api/v1/categories", json={"name": "To Delete", "type": "expense"}, headers={"Authorization": f"Bearer {token}"}, ) cat_id = create_resp.json()["id"] resp = await client.delete( f"/api/v1/categories/{cat_id}", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 204 # Category should no longer appear in listing list_resp = await client.get( "/api/v1/categories", headers={"Authorization": f"Bearer {token}"}, ) ids = [c["id"] for c in list_resp.json()] assert cat_id not in ids @pytest.mark.asyncio async def test_delete_category_with_transactions_returns_409(client: AsyncClient): _, token = await create_user_and_login(client) # Get an existing default category cat = await _get_first_category(client, token) cat_id = cat["id"] # Add a transaction linked to it await client.post( "/api/v1/transactions", json={ "amount_cents": 1000, "type": "expense", "category_id": cat_id, "transaction_date": "2026-03-15", }, headers={"Authorization": f"Bearer {token}"}, ) resp = await client.delete( f"/api/v1/categories/{cat_id}", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 409 @pytest.mark.asyncio async def test_filter_categories_by_type(client: AsyncClient): _, token = await create_user_and_login(client) resp = await client.get( "/api/v1/categories?type=income", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 for c in resp.json(): assert c["type"] == "income" @pytest.mark.asyncio async def test_category_isolation_between_users(client: AsyncClient): """User A cannot see or modify User B's categories.""" _, token_a = await create_user_and_login(client, email="a@example.com") _, token_b = await create_user_and_login(client, email="b@example.com") # User A creates a category resp = await client.post( "/api/v1/categories", json={"name": "A's private", "type": "expense"}, headers={"Authorization": f"Bearer {token_a}"}, ) cat_id = resp.json()["id"] # User B cannot delete it del_resp = await client.delete( f"/api/v1/categories/{cat_id}", headers={"Authorization": f"Bearer {token_b}"}, ) assert del_resp.status_code == 404 @pytest.mark.asyncio async def test_get_nonexistent_category_returns_404(client: AsyncClient): _, token = await create_user_and_login(client) fake_id = "00000000-0000-0000-0000-000000000000" resp = await client.delete( f"/api/v1/categories/{fake_id}", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 404