Finance-Accounts/ar-aging-app/backend/tests/test_month_locking.py

118 lines
5.1 KiB
Python

"""One closing per month (guarded), completed closings are read-only, reopen unlocks."""
from __future__ import annotations
import os
import tempfile
from fastapi.testclient import TestClient
from app.api.main import app
from app.db import models
from app.db.database import SessionLocal, init_db
from tests.test_excel_export import make_amazon_xlsx
_TMP = tempfile.mkdtemp(prefix="ar_lock_test_")
def _processed(c, name: str, month_end: str) -> int:
sid = c.post("/api/sessions", json={"name": name, "month_end_date": month_end,
"allow_duplicate": True}).json()["id"]
path = os.path.join(_TMP, f"USA {name}.xlsx")
make_amazon_xlsx(path, order_rows=5)
with open(path, "rb") as fh:
assert c.post(f"/api/sessions/{sid}/files",
files={"files": (os.path.basename(path), fh)}).status_code == 200
assert c.post(f"/api/sessions/{sid}/process").status_code == 200
assert c.get(f"/api/sessions/{sid}/status").json()["status"] == "processed"
return sid
def _force_complete(sid: int) -> None:
db = SessionLocal()
try:
db.get(models.Session, sid).status = "completed"
db.commit()
finally:
db.close()
def test_duplicate_month_is_refused_unless_explicit():
init_db()
with TestClient(app) as c:
first = c.post("/api/sessions", json={"name": "dup guard A",
"month_end_date": "2028-01-31"})
assert first.status_code == 200, first.text
again = c.post("/api/sessions", json={"name": "dup guard B",
"month_end_date": "2028-01-31"})
assert again.status_code == 409
assert "2028-01" in again.json()["detail"]
assert "dup guard A" in again.json()["detail"] # names the existing closing
forced = c.post("/api/sessions", json={"name": "dup guard C",
"month_end_date": "2028-01-31",
"allow_duplicate": True})
assert forced.status_code == 200
# The listing flags both sessions as sharing a month.
rows = c.get("/api/sessions").json()
flagged = [s for s in rows if s["reporting_month"] == "2028-01"]
assert len(flagged) == 2 and all(s["duplicate_month"] for s in flagged)
def test_listing_is_month_ordered_with_publish_flag():
init_db()
with TestClient(app) as c:
c.post("/api/sessions", json={"name": "older", "month_end_date": "2028-02-29",
"allow_duplicate": True})
c.post("/api/sessions", json={"name": "newer", "month_end_date": "2028-03-31",
"allow_duplicate": True})
rows = c.get("/api/sessions").json()
months = [s["reporting_month"] for s in rows if s["reporting_month"]]
assert months == sorted(months, reverse=True)
assert all("journal_approved" in s for s in rows)
def test_completed_closing_is_locked_and_reopen_unlocks():
init_db()
with TestClient(app) as c:
sid = _processed(c, "lock me", "2028-04-30")
_force_complete(sid)
# Every mutating surface answers 409 while completed.
path = os.path.join(_TMP, "USA lock me.xlsx")
with open(path, "rb") as fh:
up = c.post(f"/api/sessions/{sid}/files",
files={"files": ("late file.xlsx", fh)})
assert up.status_code == 409
assert c.post(f"/api/sessions/{sid}/process").status_code == 409
assert c.put(f"/api/sessions/{sid}/fx",
json=[{"marketplace": "USA", "currency": "USD", "rate": 1.0}]).status_code == 409
assert c.put(f"/api/sessions/{sid}/opening-balances",
json=[{"marketplace": "USA", "amount": 1.0}]).status_code == 409
assert c.post(f"/api/sessions/{sid}/journal/reset-signoff").status_code == 409
assert c.patch(f"/api/sessions/{sid}",
json={"clearing_lag_days": 5}).status_code == 409
# …but a rename stays allowed, and reads still work.
assert c.patch(f"/api/sessions/{sid}", json={"name": "renamed"}).status_code == 200
assert c.get(f"/api/sessions/{sid}/summary").status_code == 200
# Reopen restores editability.
assert c.post(f"/api/sessions/{sid}/reopen").status_code == 200
assert c.get(f"/api/sessions/{sid}").json()["status"] == "processed"
assert c.put(f"/api/sessions/{sid}/opening-balances",
json=[{"marketplace": "USA", "amount": 1.0}]).status_code == 200
# Reopen on a non-completed closing is refused.
assert c.post(f"/api/sessions/{sid}/reopen").status_code == 409
def test_accounts_summary_lists_unpublished_months_as_pending():
init_db()
with TestClient(app) as c:
sid = _processed(c, "pending month", "2028-05-31")
summ = c.get("/api/accounts-summary").json()
mine = [p for p in summ["pending"] if p["session_id"] == sid]
assert len(mine) == 1
assert mine[0]["month"] == "2028-05"
assert "approv" in mine[0]["reason"] # explains HOW to publish it