Files
budget-tracker/backend/tests/test_categories.py
T
2026-03-17 16:16:08 +00:00

162 lines
4.9 KiB
Python

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