dodil/erp-brick-payroll
DODIL ERP brick: payroll
tests/test_seams.py
"""The neighbours - hr, time-off, the ledger, payments - each behind one adapter: every answer is a reply, never an exception.
Not installed (409), failed (502), silent (503). And every call this brick makes is checked against the CALLEE's signed spec
(conftest.checked_seams)."""
import pytest
from conftest import ADMIN, MANAGER, SYSTEM, api, calculate, must, onboard, open_run, peer_approves_own, refused, setup_company
from erp_platform import seam
from erp_platform.contract import ContractViolation
from brick_payroll.infrastructure import seams
def test_a_call_the_callees_spec_would_refuse_fails_the_test_at_the_call(nb, checked_seams):
with pytest.raises(ContractViolation, match="event"):
seams.LEDGER.post("/journal-entry/journals", {"journal_id": "1", "source_module": "payroll", "source_document": "prl:R", "event": "settle",
"company_code": "UK01", "journal_date": "2026-11-30", "currency": "GBP", "lines": []}, actor="x")
with pytest.raises(ContractViolation):
seams.PAYMENTS.post("/payouts", {"payout_ref": "prl:R:1:net"}, actor="x") # required fields missing
with pytest.raises(ContractViolation):
seams.HR.get("/persons/1/salary", params={}, actor="x") # a route the spec does not list
seams.HR.get("/persons/4410/work-pattern", params={"from": "2026-11-01", "to": "2026-11-30"}, actor="x") # the real one passes
assert len(checked_seams.violations) == 3
checked_seams.violations.clear()
def test_hr_down_is_a_reply_and_nothing_is_half_calculated(nb):
setup_company()
run = open_run()["pay_run_id"]
onboard(run)
nb.hr_status = 500
refused(calculate(run), 502, "hr failed")
assert must(api("GET", f"/pay-runs/{run}", ADMIN))["calculation"] == 1
nb.hr_status = None
assert must(calculate(run))["calculation"] == 2
def test_without_hr_there_is_no_run_and_without_time_off_absences_are_manual(nb):
setup_company()
run = open_run()["pay_run_id"]
seam.unregister_local("time-off")
onboard(run) # no absences to read: calculated
seam.unregister_local("hr")
refused(calculate(run), 409, "hr is not part of this installation")
def test_payments_down_leaves_the_payouts_pending_for_the_next_drain(nb):
setup_company()
nb.pay_status = 500
run = open_run()["pay_run_id"]
onboard(run)
peer_approves_own(run)
got = must(api("POST", f"/pay-runs/{run}/approve", MANAGER))
(box,) = [o for o in got["outbox"] if o["target"] == "payments"]
assert got["journal_status"] == "posted" and box["status"] == "pending" and "failed (500" in box["last_error"]
nb.pay_status = None
must(api("POST", "/outbox/drain", SYSTEM))
assert len(nb.payouts) == 4
def test_without_the_ledger_an_approved_run_waits_with_its_reason_and_pays_nobody(nb):
setup_company()
seam.unregister_local("gl")
run = open_run()["pay_run_id"]
onboard(run)
peer_approves_own(run)
got = must(api("POST", f"/pay-runs/{run}/approve", MANAGER))
assert got["status"] == "approved" and got["journal_status"] == "pending" and nb.payouts == {}
assert "no ledger is installed" in [o for o in got["outbox"] if o["target"] == "gl"][0]["last_error"]