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"]