118 lines
5.1 KiB
Python
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
|