mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-13 02:56:39 +07:00
backend/tests/: 33 pytest tests against a real Postgres database (aurum_test, created/migrated/dropped per run) exercised through the actual HTTP API — not sqlite, not mocks, and the real `aurum` database is never opened (app lifespan is skipped, get_session is overridden per test). Covers transaction category-kind validation, year/month/date-range filtering and pagination, dashboard summary arithmetic, budget status, cash flow, and the /transactions/years picker. e2e/: 6 Playwright specs driving a real browser against a fully separate throwaway docker-compose stack (run.sh: separate project name, database, port, and volume, torn down with `down -v` on exit) — regression coverage for the Recent Transactions month bug, the Card grid-overflow bug, the frozen-year bug, the Net Worth default range, and the Dashboard-> Transactions deep link, all fixed earlier this session. Also synced the backend's APP_VERSION constant (stale at 0.15.2, embedded in exported backups) with the frontend version.
83 lines
3.4 KiB
Python
83 lines
3.4 KiB
Python
"""Budgets: category-kind restriction, one-budget-per-category, and the
|
|
spent/remaining/over-budget math behind the Budget page's progress bars.
|
|
"""
|
|
from decimal import Decimal
|
|
|
|
from httpx import AsyncClient
|
|
|
|
from tests.helpers import money, txn_payload
|
|
|
|
|
|
async def test_budget_rejects_income_category(client: AsyncClient, categories):
|
|
resp = await client.post(
|
|
"/budgets", json={"category_id": categories["Salary"]["id"], "monthly_limit": "500.00"}
|
|
)
|
|
assert resp.status_code == 400
|
|
|
|
|
|
async def test_budget_rejects_duplicate_category(client: AsyncClient, categories):
|
|
category_id = categories["Groceries"]["id"]
|
|
first = await client.post("/budgets", json={"category_id": category_id, "monthly_limit": "300.00"})
|
|
assert first.status_code == 201
|
|
|
|
second = await client.post("/budgets", json={"category_id": category_id, "monthly_limit": "400.00"})
|
|
assert second.status_code == 400
|
|
|
|
|
|
async def test_status_reports_spent_remaining_and_over_budget(client: AsyncClient, account_id, categories):
|
|
category_id = categories["Groceries"]["id"]
|
|
await client.post("/budgets", json={"category_id": category_id, "monthly_limit": "100.00"})
|
|
|
|
for amount in ["60.00", "60.00"]: # 120.00 total, over a 100.00 limit
|
|
await client.post(
|
|
"/transactions",
|
|
json=txn_payload(account_id, amount=amount, category_id=category_id, date="2026-08-10"),
|
|
)
|
|
|
|
resp = await client.get("/budgets/status", params={"year": 2026, "month": 8})
|
|
item = resp.json()["items"][0]
|
|
assert money(item["spent"]) == Decimal("120.00")
|
|
assert money(item["remaining"]) == Decimal("-20.00")
|
|
assert item["percent"] == 120.0
|
|
assert item["is_over_budget"] is True
|
|
|
|
|
|
async def test_status_only_counts_the_selected_month(client: AsyncClient, account_id, categories):
|
|
category_id = categories["Groceries"]["id"]
|
|
await client.post("/budgets", json={"category_id": category_id, "monthly_limit": "100.00"})
|
|
await client.post(
|
|
"/transactions", json=txn_payload(account_id, amount="90.00", category_id=category_id, date="2026-07-15")
|
|
)
|
|
|
|
resp = await client.get("/budgets/status", params={"year": 2026, "month": 8})
|
|
item = resp.json()["items"][0]
|
|
assert money(item["spent"]) == 0
|
|
assert item["is_over_budget"] is False
|
|
|
|
|
|
async def test_status_is_empty_list_when_no_budgets_exist(client: AsyncClient):
|
|
resp = await client.get("/budgets/status", params={"year": 2026, "month": 8})
|
|
assert resp.json()["items"] == []
|
|
|
|
|
|
async def test_update_budget_changes_the_limit(client: AsyncClient, categories):
|
|
category_id = categories["Groceries"]["id"]
|
|
created = await client.post("/budgets", json={"category_id": category_id, "monthly_limit": "100.00"})
|
|
budget_id = created.json()["id"]
|
|
|
|
resp = await client.patch(f"/budgets/{budget_id}", json={"monthly_limit": "250.00"})
|
|
assert resp.status_code == 200
|
|
assert money(resp.json()["monthly_limit"]) == Decimal("250.00")
|
|
|
|
|
|
async def test_delete_budget_removes_it_from_status(client: AsyncClient, account_id, categories):
|
|
category_id = categories["Groceries"]["id"]
|
|
created = await client.post("/budgets", json={"category_id": category_id, "monthly_limit": "100.00"})
|
|
budget_id = created.json()["id"]
|
|
|
|
delete_resp = await client.delete(f"/budgets/{budget_id}")
|
|
assert delete_resp.status_code == 204
|
|
|
|
status = await client.get("/budgets/status", params={"year": 2026, "month": 8})
|
|
assert status.json()["items"] == []
|