Files
Budget_Tool_Martinhal/backend/tests/test_api.py
T

114 lines
5.8 KiB
Python

from io import BytesIO
import openpyxl
import pytest
# Category totals from the "Capex e Custos 2027 v4" PDF. Lines are rounded to cents before
# summing, so a category can differ from the PDF by a cent or two.
PDF_TOTALS = {
"Contract Services": (600758.54, 615940.02),
"Cost of Cell Phones": (96532.00, 111011.80),
"Cost of Internet Services": (74160.00, 85284.00),
"Cost of Local Calls": (5575.00, 6269.13),
"Cost of Long Distance Calls": (11415.00, 12488.47),
"Other Cost of Services": (176847.00, 185226.36),
"Hardware": (37636.00, 83361.00),
"Property Operation and Maintenance": (31283.00, 55015.83),
"Other IT Equipment": (72566.00, 70747.24),
"Travel-Other": (14400.00, 16880.00),
}
def test_requires_login():
from fastapi.testclient import TestClient
from app.main import app
assert TestClient(app).get("/api/years").status_code == 401
def test_writes_need_header(client):
r = client.post("/api/years", json={"year": 2030}, headers={"X-Requested-With": ""})
assert r.status_code == 403
def test_import_matches_pdf(client, imported):
y = client.get(f"/api/years/{imported}").json()
assert y["year"] == 2027 and y["default_rate"] == pytest.approx(0.055)
got = {c["category"]["name_en"]: c["totals"] for c in y["categories"] if c["lines"]}
for name, (prior, adjusted) in PDF_TOTALS.items():
assert got[name]["prior"] == pytest.approx(prior, abs=0.005)
assert got[name]["adjusted"] == pytest.approx(adjusted, abs=0.02)
assert y["costs"]["prior"] == pytest.approx(1121172.54, abs=0.005)
assert y["costs"]["adjusted"] == pytest.approx(1242223.85, abs=0.05)
assert sum(len(c["lines"]) for c in y["categories"]) == 54
hw = next(c for c in y["categories"] if c["category"]["name_en"] == "Hardware")
oriente = next(l for l in hw["lines"] if "ORIENTE" in l["name"])
assert oriente["adj_kind"] == "factor" and oriente["adj_value"] == pytest.approx(3.055)
assert "Hardware price increase" in oriente["adj_reason"]
protel = next(l for c in y["categories"] for l in c["lines"] if l["name"] == "Protel")
assert protel["prior_expr"] == "5512*12"
def test_line_crud_and_month_spread(client, imported):
cats = client.get("/api/categories").json()
training = next(c for c in cats if c["name_en"] == "Training")
r = client.post(f"/api/years/{imported}/lines", json={
"category_id": training["id"], "name": "Microsoft certification", "prior_expr": "1000",
"base_expr": "=4*300", "adj_kind": "percent", "adj_value": 10})
assert r.status_code == 200, r.text
line_id = r.json()["id"]
y = client.get(f"/api/years/{imported}").json()
line = next(l for c in y["categories"] for l in c["lines"] if l["id"] == line_id)
assert line["base"] == 1200 and line["adjusted"] == 1320 and sum(line["months"]) == pytest.approx(1320)
assert client.put(f"/api/lines/{line_id}", json={"base_expr": "abc"}).status_code == 400
r = client.put(f"/api/lines/{line_id}", json={"monthly_kind": "manual"})
assert r.status_code == 200
y = client.get(f"/api/years/{imported}").json()
line = next(l for c in y["categories"] for l in c["lines"] if l["id"] == line_id)
assert line["monthly_values"][0] == 110 and line["adjusted"] == pytest.approx(1320)
assert client.delete(f"/api/lines/{line_id}").status_code == 200
audit = client.get(f"/api/years/{imported}/audit").json()
assert [a["action"] for a in audit[:3]] == ["delete", "update", "create"]
def test_status_workflow_and_roles(client, imported):
it = client.get("/api/departments").json()[0]["id"]
r = client.post("/api/users", json={"email": "ed@test.local", "name": "Ed", "role": "editor",
"password": "editor-pass-1", "department_id": it})
assert r.status_code == 200
from fastapi.testclient import TestClient
from app.main import app
ed = TestClient(app)
ed.headers.update({"X-Requested-With": "budget-app"})
assert ed.post("/api/auth/login", json={"email": "ed@test.local", "password": "editor-pass-1"}).status_code == 200
assert ed.get("/api/users").status_code == 403
assert ed.post(f"/api/years/{imported}/status", json={"action": "submit"}).status_code == 200
assert ed.post(f"/api/years/{imported}/status", json={"action": "approve"}).status_code == 403
line = client.get(f"/api/years/{imported}").json()["categories"][1]["lines"][0]
assert ed.put(f"/api/lines/{line['id']}", json={"name": "x"}).status_code == 409
assert client.post(f"/api/years/{imported}/status", json={"action": "approve"}).json()["status"] == "approved"
assert client.post(f"/api/years/{imported}/status", json={"action": "reopen"}).status_code == 400 # reason needed
assert client.post(f"/api/years/{imported}/status", json={"action": "reopen", "comment": "new contract"}).json()["status"] == "draft"
def test_rollover(client, imported):
r = client.post("/api/years", json={"year": 2028, "copy_from_id": imported})
assert r.status_code == 200
y = client.get(f"/api/years/{r.json()['id']}").json()
assert y["costs"]["prior"] == pytest.approx(1242223.85, abs=0.05)
@pytest.mark.parametrize("kind", ["cost-report", "budget"])
@pytest.mark.parametrize("lang", ["pt", "en"])
def test_reports(client, imported, kind, lang):
html = client.get(f"/api/years/{imported}/reports/{kind}.html?lang={lang}")
assert html.status_code == 200
assert ("<svg" if kind == "cost-report" else "<table") in html.text
xlsx = client.get(f"/api/years/{imported}/reports/{kind}.xlsx?lang={lang}")
assert xlsx.status_code == 200
wb = openpyxl.load_workbook(BytesIO(xlsx.content))
assert len(wb.sheetnames) >= 2
pdf = client.get(f"/api/years/{imported}/reports/{kind}.pdf?lang={lang}")
assert pdf.status_code == 200 and pdf.content[:4] == b"%PDF"