Files
LedgerLine/tests/test_contract_transactions.py
T
Movses Aslanyan ccf1783779 Add Phase 0a black-box integration test suite
81 tests across 11 files plus a 5-module harness (load, pg, streams,
containers, payloads), covering all eleven Phase 0 goals and all 13 named
chaos-flag pathologies. Talks to the system only over HTTP, Postgres, Redis,
and the Docker CLI -- no imports of application code. RED-BASELINE.md records
the suite failing red against the empty repo before any implementation
existed; README.md documents the request-contract fields invented to fill
gaps in the spec, measurement methodology, and known-flaky tests.

This suite is a frozen contract from this point forward per CLAUDE.md.
2026-09-05 08:11:59 +03:30

141 lines
5.5 KiB
Python

"""G1 -- a transaction traverses all seven services and returns a coherent result."""
import time
import uuid
import psycopg
import pytest
from harness.payloads import tx_payload
pytestmark = pytest.mark.contract
async def test_valid_transaction_returns_full_envelope(api, manifest, fresh_balances):
"""G1: a valid transaction returns 201 with the full envelope and coherent fields."""
account_id = manifest["clean_account_id"]
fresh_balances(account_id)
resp = await api.post("/api/transactions", json=tx_payload(account_id))
assert resp.status_code == 201, resp.text
body = resp.json()
uuid.UUID(body["transaction_id"])
assert body["status"] in ("settled", "rejected")
assert 0.0 <= body["risk_score"] <= 1.0
assert isinstance(body["balance_minor"], int)
assert "summary" in body
uuid.UUID(body["request_id"])
assert body["elapsed_ms"] > 0
async def test_transaction_is_durable(api, manifest, fresh_balances, dsn):
"""G1: the transaction row exists with a terminal status within 2s."""
account_id = manifest["clean_account_id"]
fresh_balances(account_id)
resp = await api.post("/api/transactions", json=tx_payload(account_id))
assert resp.status_code == 201
tx_id = resp.json()["transaction_id"]
deadline = time.monotonic() + 2
status = None
with psycopg.connect(dsn, autocommit=True) as conn:
while time.monotonic() < deadline:
with conn.cursor() as cur:
cur.execute("SELECT status FROM transactions WHERE id = %s", (tx_id,))
row = cur.fetchone()
if row and row[0] in ("settled", "rejected"):
status = row[0]
break
assert status in ("settled", "rejected"), f"transaction {tx_id} never reached a terminal status"
async def test_deposit_increases_balance_by_exact_amount(api, manifest, fresh_balances, dsn):
"""G1: a settled deposit moves the balance up by exactly amount_minor."""
account_id = manifest["clean_account_id"]
fresh_balances(account_id)
with psycopg.connect(dsn, autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute("SELECT balance_minor FROM accounts WHERE id = %s", (account_id,))
(before,) = cur.fetchone()
amount = 5000
resp = await api.post(
"/api/transactions", json=tx_payload(account_id, kind="deposit", amount_minor=amount)
)
assert resp.status_code == 201
body = resp.json()
if body["status"] == "settled":
assert body["balance_minor"] == before + amount
async def test_withdrawal_decreases_balance_by_exact_amount(api, manifest, fresh_balances, dsn):
"""G1: a settled withdrawal moves the balance down by exactly amount_minor."""
account_id = manifest["clean_account_id"]
fresh_balances(account_id)
with psycopg.connect(dsn, autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute("SELECT balance_minor FROM accounts WHERE id = %s", (account_id,))
(before,) = cur.fetchone()
amount = 1000
resp = await api.post(
"/api/transactions", json=tx_payload(account_id, kind="withdrawal", amount_minor=amount)
)
assert resp.status_code == 201
body = resp.json()
if body["status"] == "settled":
assert body["balance_minor"] == before - amount
async def test_sanctioned_holder_scores_higher_risk_than_clean(api, manifest, fresh_balances):
"""G1: a sanctioned account yields a materially higher risk_score than a
clean one (assert the ordering, not a value)."""
clean_id = manifest["clean_account_id"]
sanctioned_id = manifest["sanctioned_account_ids"][0]
fresh_balances(clean_id)
fresh_balances(sanctioned_id)
clean_resp = await api.post("/api/transactions", json=tx_payload(clean_id))
sanctioned_resp = await api.post("/api/transactions", json=tx_payload(sanctioned_id))
assert clean_resp.status_code == 201
assert sanctioned_resp.status_code == 201
clean_score = clean_resp.json()["risk_score"]
sanctioned_score = sanctioned_resp.json()["risk_score"]
assert sanctioned_score > clean_score, (
f"expected sanctioned account risk_score ({sanctioned_score}) > clean ({clean_score})"
)
async def test_insights_returns_summary_for_seeded_account(api, manifest):
"""G1: GET /api/accounts/{id}/insights returns a summary for a seeded account."""
resp = await api.get(f"/api/accounts/{manifest['clean_account_id']}/insights")
assert resp.status_code == 200
assert isinstance(resp.json(), dict) and resp.json()
async def test_unknown_account_returns_404(api):
"""G1: submitting a transaction for an unknown account returns 404 with the error envelope."""
resp = await api.post("/api/transactions", json=tx_payload(str(uuid.uuid4())))
assert resp.status_code == 404
body = resp.json()
assert "error" in body and "request_id" in body
async def test_negative_amount_on_deposit_is_rejected(api, manifest):
"""G1: a negative amount_minor on a deposit returns 400 or 422."""
resp = await api.post(
"/api/transactions",
json=tx_payload(manifest["clean_account_id"], kind="deposit", amount_minor=-500),
)
assert resp.status_code in (400, 422), resp.text
async def test_missing_required_field_returns_422(api, manifest):
"""G1: a missing required field returns 422."""
payload = tx_payload(manifest["clean_account_id"])
del payload["kind"]
resp = await api.post("/api/transactions", json=payload)
assert resp.status_code == 422