fin-hub/tests/test_billing.py

138 lines
5.2 KiB
Python

import sqlite3
from decimal import Decimal
from pathlib import Path
from fin_hub.services.billing import build_billing_basis
from fin_hub.services.exchange import ingest_planning_evidence
from fin_hub.services.ledger import import_csv, record_engagement_price
CLIENT_KEY = "client:acme|app:portal|instance:prod-01"
def _seed_billing_ledger(tmp_path: Path):
ledger = tmp_path / "ledger.db"
direct = tmp_path / "direct.csv"
direct.write_text(
"product,amount,currency,invoice_date,environment,client_id,application_id,app_instance_id\n"
"Dedicated service,20.00,EUR,2026-07-01,production,acme,portal,prod-01\n",
encoding="utf-8",
)
shared = tmp_path / "shared.csv"
shared.write_text(
"product,amount,currency,invoice_date,environment\n"
"Shared cluster,100.00,EUR,2026-07-01,production\n",
encoding="utf-8",
)
import_csv(direct, "hosteurope", ledger_path=ledger)
import_csv(shared, "hosteurope", ledger_path=ledger)
with sqlite3.connect(ledger) as conn:
shared_fact_id = conn.execute(
"SELECT financial_fact_id FROM ledger_entries "
"WHERE cost_attribution_key IS NULL"
).fetchone()[0]
price = record_engagement_price(
client_id="acme",
application_id="portal",
app_instance_id="prod-01",
period_month="2026-07",
amount="200.00",
currency="EUR",
source="agreement-v1",
ledger_path=ledger,
)
allocation = {
"schema_version": "0.1",
"record_type": "allocation",
"record_id": "allocation:shared:2026-07:v1",
"revision_of": None,
"resource_id": "resource:shared_cluster",
"service_id": "shared-cluster",
"workload_id": None,
"tenant_id": None,
"environment": "production",
"cost_attribution_key": "platform:shared-cluster",
"period_start": "2026-07-01",
"period_end": "2026-07-31",
"currency": "EUR",
"source_evidence": ["resource-control:namespace-cpu-v1"],
"created_at": "2026-08-11T10:00:00Z",
"financial_fact_ids": [shared_fact_id],
"method": "namespace-cpu-v1",
"allocated_amount": "100.00",
"shares": [{"target_key": CLIENT_KEY, "share": "0.80"}],
"residual_share": "0.20",
}
ingest_planning_evidence(allocation, ledger_path=ledger)
return ledger, direct, price, allocation
def test_billing_basis_combines_direct_and_allocated_cost_idempotently(tmp_path: Path):
ledger, _direct, price, allocation = _seed_billing_ledger(tmp_path)
first = build_billing_basis(ledger_path=ledger)
second = build_billing_basis(ledger_path=ledger)
assert first == second
assert first.artifact_type == "billing_basis_report"
assert "not an invoice" in first.disclaimer
record = first.records[0]
assert record.price_id == price.id
assert record.revenue == Decimal("200.00")
assert record.direct_cost == Decimal("20.00")
assert record.allocated_cost == Decimal("80.00")
assert record.total_cost == Decimal("100.00")
assert record.margin == Decimal("100.00")
assert record.allocation_ids == (allocation["record_id"],)
assert record.financial_fact_ids
assert "agreement-v1" in record.provenance
assert first.exceptions[0].reason == "unattributed_allocation_residual"
assert first.exceptions[0].amount == Decimal("20.00")
def test_billing_basis_id_changes_and_exposes_correction_and_price_revision(tmp_path: Path):
ledger, direct, price, _allocation = _seed_billing_ledger(tmp_path)
before = build_billing_basis(ledger_path=ledger).records[0]
direct.write_text(
"product,amount,currency,invoice_date,environment,client_id,application_id,app_instance_id\n"
"Dedicated service,25.00,EUR,2026-07-01,production,acme,portal,prod-01\n",
encoding="utf-8",
)
import_csv(direct, "hosteurope", ledger_path=ledger, force=True)
revised_price = record_engagement_price(
client_id="acme",
application_id="portal",
app_instance_id="prod-01",
period_month="2026-07",
amount="210.00",
currency="EUR",
source="agreement-v2",
revision_of=price.id,
ledger_path=ledger,
)
after = build_billing_basis(ledger_path=ledger).records[0]
assert after.billing_basis_id != before.billing_basis_id
assert after.price_id == revised_price.id
assert after.price_revision_of == price.id
assert after.financial_fact_corrections
assert after.direct_cost == Decimal("25.00")
assert after.margin == Decimal("105.00")
def test_client_cost_without_price_is_an_explicit_exception(tmp_path: Path):
ledger = tmp_path / "ledger.db"
source = tmp_path / "direct.csv"
source.write_text(
"product,amount,currency,invoice_date,client_id,application_id,app_instance_id\n"
"Dedicated service,20.00,EUR,2026-07-01,acme,portal,prod-01\n",
encoding="utf-8",
)
import_csv(source, "hosteurope", ledger_path=ledger)
export = build_billing_basis(ledger_path=ledger)
assert export.records == ()
assert export.exceptions[0].reason == "missing_price_basis"
assert export.exceptions[0].cost_attribution_key == CLIENT_KEY
assert export.exceptions[0].amount == Decimal("20.00")