114 lines
5.8 KiB
Python
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"
|