mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-13 02:56:39 +07:00
test: real backend pytest + E2E Playwright suites, both isolated from prod data
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.
This commit is contained in:
82
backend/tests/test_budgets.py
Normal file
82
backend/tests/test_budgets.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""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"] == []
|
||||
Reference in New Issue
Block a user