coming together
This commit is contained in:
263
tests/test_financial_api.py
Normal file
263
tests/test_financial_api.py
Normal file
@@ -0,0 +1,263 @@
|
||||
import os
|
||||
import uuid
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
# Ensure required env vars for app import/config
|
||||
os.environ.setdefault("SECRET_KEY", "x" * 32)
|
||||
os.environ.setdefault("DATABASE_URL", "sqlite:////tmp/delphi_test.sqlite")
|
||||
|
||||
from app.main import app # noqa: E402
|
||||
from app.auth.security import get_current_user # noqa: E402
|
||||
from tests.helpers import assert_validation_error, assert_http_error # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def client():
|
||||
# Override auth to bypass JWT for these tests
|
||||
class _User:
|
||||
def __init__(self):
|
||||
self.id = "test"
|
||||
self.username = "tester"
|
||||
self.is_admin = True
|
||||
self.is_active = True
|
||||
|
||||
app.dependency_overrides[get_current_user] = lambda: _User()
|
||||
|
||||
try:
|
||||
yield TestClient(app)
|
||||
finally:
|
||||
app.dependency_overrides.pop(get_current_user, None)
|
||||
|
||||
|
||||
def _create_customer(client: TestClient) -> str:
|
||||
customer_id = f"LEDGER-CUST-{uuid.uuid4().hex[:8]}"
|
||||
payload = {"id": customer_id, "last": "LedgerOwner", "email": "owner@example.com"}
|
||||
resp = client.post("/api/customers/", json=payload)
|
||||
assert resp.status_code == 200
|
||||
return customer_id
|
||||
|
||||
|
||||
def _create_file(client: TestClient, owner_id: str) -> str:
|
||||
file_no = f"L-{uuid.uuid4().hex[:6]}"
|
||||
payload = {
|
||||
"file_no": file_no,
|
||||
"id": owner_id,
|
||||
"regarding": "Ledger matter",
|
||||
"empl_num": "E01",
|
||||
"file_type": "CIVIL",
|
||||
"opened": date.today().isoformat(),
|
||||
"status": "ACTIVE",
|
||||
"rate_per_hour": 100.0,
|
||||
"memo": "Created by pytest",
|
||||
}
|
||||
resp = client.post("/api/files/", json=payload)
|
||||
assert resp.status_code == 200
|
||||
return file_no
|
||||
|
||||
|
||||
def test_ledger_create_validation_errors(client: TestClient):
|
||||
owner_id = _create_customer(client)
|
||||
file_no = _create_file(client, owner_id)
|
||||
|
||||
# Missing required fields
|
||||
resp = client.post("/api/financial/ledger/", json={})
|
||||
assert_validation_error(resp, "file_no")
|
||||
|
||||
# Wrong types for amount/date
|
||||
bad = {
|
||||
"file_no": file_no,
|
||||
"date": "not-a-date",
|
||||
"t_code": "TIME",
|
||||
"t_type": "2",
|
||||
"empl_num": "E01",
|
||||
"quantity": 1.5,
|
||||
"rate": 100.0,
|
||||
"amount": "one hundred",
|
||||
"billed": "N",
|
||||
"note": "Invalid types",
|
||||
}
|
||||
resp = client.post("/api/financial/ledger/", json=bad)
|
||||
assert_validation_error(resp, "date")
|
||||
assert_validation_error(resp, "amount")
|
||||
|
||||
# Query param validation on list endpoint
|
||||
resp = client.get(f"/api/financial/ledger/{file_no}?limit=0")
|
||||
assert_validation_error(resp, "limit")
|
||||
resp = client.get(f"/api/financial/ledger/{file_no}?skip=-1")
|
||||
assert_validation_error(resp, "skip")
|
||||
|
||||
|
||||
def test_ledger_404s_for_missing_file_and_entries(client: TestClient):
|
||||
# Create against missing file
|
||||
payload = {
|
||||
"file_no": "NOFILE-123",
|
||||
"date": date.today().isoformat(),
|
||||
"t_code": "TIME",
|
||||
"t_type": "2",
|
||||
"empl_num": "E01",
|
||||
"quantity": 1.0,
|
||||
"rate": 100.0,
|
||||
"amount": 100.0,
|
||||
"billed": "N",
|
||||
"note": "Should 404",
|
||||
}
|
||||
resp = client.post("/api/financial/ledger/", json=payload)
|
||||
assert_http_error(resp, 404, "File not found")
|
||||
|
||||
# Update/delete missing entry id
|
||||
resp = client.put("/api/financial/ledger/9999999", json={"amount": 10})
|
||||
assert_http_error(resp, 404, "Ledger entry not found")
|
||||
resp = client.delete("/api/financial/ledger/9999999")
|
||||
assert_http_error(resp, 404, "Ledger entry not found")
|
||||
|
||||
# Report and quick endpoints on missing file
|
||||
resp = client.get("/api/financial/reports/NOFILE-123")
|
||||
assert_http_error(resp, 404, "File not found")
|
||||
resp = client.post("/api/financial/time-entry/quick", params={"file_no": "NOFILE-123", "hours": 1.0, "description": "x"})
|
||||
assert_http_error(resp, 404, "File not found")
|
||||
resp = client.post("/api/financial/payments/", params={"file_no": "NOFILE-123", "amount": 10.0})
|
||||
assert_http_error(resp, 404, "File not found")
|
||||
resp = client.post("/api/financial/expenses/", params={"file_no": "NOFILE-123", "amount": 10.0, "description": "x"})
|
||||
assert_http_error(resp, 404, "File not found")
|
||||
|
||||
# Bill entries with no matching ids (body expects a raw JSON array)
|
||||
resp = client.post("/api/financial/bill-entries", json=[999999])
|
||||
assert_http_error(resp, 404, "No entries found")
|
||||
|
||||
|
||||
def test_ledger_totals_update_after_crud(client: TestClient):
|
||||
owner_id = _create_customer(client)
|
||||
file_no = _create_file(client, owner_id)
|
||||
|
||||
# Baseline
|
||||
resp = client.get(f"/api/files/{file_no}")
|
||||
assert resp.status_code == 200
|
||||
file_data = resp.json()
|
||||
assert file_data["total_charges"] == 0
|
||||
assert file_data["amount_owing"] == 0
|
||||
|
||||
# 1) Create hourly time entry (t_type "2")
|
||||
t_payload = {
|
||||
"file_no": file_no,
|
||||
"date": date.today().isoformat(),
|
||||
"t_code": "TIME",
|
||||
"t_type": "2",
|
||||
"empl_num": "E01",
|
||||
"quantity": 2.0,
|
||||
"rate": 100.0,
|
||||
"amount": 200.0,
|
||||
"billed": "N",
|
||||
"note": "Work",
|
||||
}
|
||||
resp = client.post("/api/financial/ledger/", json=t_payload)
|
||||
assert resp.status_code == 200
|
||||
time_entry = resp.json()
|
||||
|
||||
resp = client.get(f"/api/files/{file_no}")
|
||||
f = resp.json()
|
||||
assert f["hours"] == 2.0
|
||||
assert f["hourly_fees"] == 200.0
|
||||
assert f["total_charges"] == 200.0
|
||||
assert f["amount_owing"] == 200.0
|
||||
|
||||
# 2) Create disbursement (t_type "4") amount 50
|
||||
d_payload = {
|
||||
"file_no": file_no,
|
||||
"date": date.today().isoformat(),
|
||||
"t_code": "MISC",
|
||||
"t_type": "4",
|
||||
"empl_num": "E01",
|
||||
"quantity": 0.0,
|
||||
"rate": 0.0,
|
||||
"amount": 50.0,
|
||||
"billed": "N",
|
||||
"note": "Expense",
|
||||
}
|
||||
resp = client.post("/api/financial/ledger/", json=d_payload)
|
||||
assert resp.status_code == 200
|
||||
disb_entry = resp.json()
|
||||
|
||||
resp = client.get(f"/api/files/{file_no}")
|
||||
f = resp.json()
|
||||
assert f["disbursements"] == 50.0
|
||||
assert f["total_charges"] == 250.0
|
||||
assert f["amount_owing"] == 250.0
|
||||
|
||||
# 3) Create credit/payment (t_type "5") amount 100
|
||||
c_payload = {
|
||||
"file_no": file_no,
|
||||
"date": date.today().isoformat(),
|
||||
"t_code": "PMT",
|
||||
"t_type": "5",
|
||||
"empl_num": "E01",
|
||||
"quantity": 0.0,
|
||||
"rate": 0.0,
|
||||
"amount": 100.0,
|
||||
"billed": "Y",
|
||||
"note": "Payment",
|
||||
}
|
||||
resp = client.post("/api/financial/ledger/", json=c_payload)
|
||||
assert resp.status_code == 200
|
||||
credit_entry = resp.json()
|
||||
|
||||
resp = client.get(f"/api/files/{file_no}")
|
||||
f = resp.json()
|
||||
assert f["credit_bal"] == 100.0
|
||||
assert f["amount_owing"] == 150.0
|
||||
|
||||
# 4) Trust deposit (t_type "1") amount 80
|
||||
trust_payload = {
|
||||
"file_no": file_no,
|
||||
"date": date.today().isoformat(),
|
||||
"t_code": "TRUST",
|
||||
"t_type": "1",
|
||||
"empl_num": "E01",
|
||||
"quantity": 0.0,
|
||||
"rate": 0.0,
|
||||
"amount": 80.0,
|
||||
"billed": "Y",
|
||||
"note": "Trust deposit",
|
||||
}
|
||||
resp = client.post("/api/financial/ledger/", json=trust_payload)
|
||||
assert resp.status_code == 200
|
||||
trust_entry = resp.json()
|
||||
|
||||
resp = client.get(f"/api/files/{file_no}")
|
||||
f = resp.json()
|
||||
assert f["trust_bal"] == 80.0
|
||||
assert f["transferable"] == 80.0
|
||||
|
||||
# 5) Update credit entry to 200
|
||||
resp = client.put(f"/api/financial/ledger/{credit_entry['id']}", json={"amount": 200.0})
|
||||
assert resp.status_code == 200
|
||||
|
||||
resp = client.get(f"/api/files/{file_no}")
|
||||
f = resp.json()
|
||||
assert f["credit_bal"] == 200.0
|
||||
assert f["amount_owing"] == 50.0
|
||||
assert f["transferable"] == 50.0
|
||||
|
||||
# 6) Delete trust deposit, transferable should drop to 0
|
||||
resp = client.delete(f"/api/financial/ledger/{trust_entry['id']}")
|
||||
assert resp.status_code == 200
|
||||
|
||||
resp = client.get(f"/api/files/{file_no}")
|
||||
f = resp.json()
|
||||
assert f["trust_bal"] == 0.0
|
||||
assert f["transferable"] == 0.0
|
||||
|
||||
# 7) Verify report totals consistency
|
||||
resp = client.get(f"/api/financial/reports/{file_no}")
|
||||
assert resp.status_code == 200
|
||||
report = resp.json()
|
||||
assert report["total_hours"] == 2.0
|
||||
assert report["total_hourly_fees"] == 200.0
|
||||
assert report["total_disbursements"] == 50.0
|
||||
assert report["total_credits"] == 200.0
|
||||
assert report["total_charges"] == 250.0
|
||||
assert report["amount_owing"] == 50.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user