99 lines
3.5 KiB
Python
99 lines
3.5 KiB
Python
"""Refund calculator unit tests (ticket 017).
|
|
|
|
Table-driven tests for the pure ``calculate_refund`` function.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from contract_check.core.billing.refunds import RefundCalc, calculate_refund
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
def _invoice(
|
|
*,
|
|
status: str = "succeeded",
|
|
kind: str = "topup",
|
|
amount: int = 19900,
|
|
credits_purchased: int = 1,
|
|
paid_at: dt.datetime | None = None,
|
|
) -> dict:
|
|
return {
|
|
"id": uuid.uuid4(),
|
|
"status": status,
|
|
"kind": kind,
|
|
"amount": amount,
|
|
"credits_purchased": credits_purchased,
|
|
"paid_at": paid_at or dt.datetime.now(tz=dt.UTC),
|
|
"user_id": uuid.uuid4(),
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"purchased, used, amount, price, expected",
|
|
[
|
|
(20, 0, 398000, 19900, RefundCalc(398000, "full", "within full-refund usage window", 0)),
|
|
(20, 3, 398000, 19900, RefundCalc(398000, "full", "within full-refund usage window", 3)),
|
|
(20, 5, 398000, 19900, RefundCalc(298500, "proportional", "used 5 of 20", 5)),
|
|
(20, 20, 398000, 19900, RefundCalc(0, "zero", "usage exceeds paid value", 20)),
|
|
(20, 25, 398000, 19900, RefundCalc(0, "zero", "usage exceeds paid value", 25)),
|
|
(1, 0, 19900, 19900, RefundCalc(19900, "full", "within full-refund usage window", 0)),
|
|
],
|
|
)
|
|
def test_calculate_refund_full_and_proportional(
|
|
purchased: int, used: int, amount: int, price: int, expected: RefundCalc
|
|
) -> None:
|
|
invoice = _invoice(amount=amount, credits_purchased=purchased)
|
|
result = calculate_refund(
|
|
invoice, used, purchased_documents=purchased, price_per_doc_kopecks=price
|
|
)
|
|
assert result.amount_kopecks == expected.amount_kopecks
|
|
assert result.kind == expected.kind
|
|
assert result.used_documents == used
|
|
|
|
|
|
def test_calculate_refund_already_refunded() -> None:
|
|
invoice = _invoice(status="refunded", credits_purchased=20)
|
|
result = calculate_refund(invoice, 0, purchased_documents=20, price_per_doc_kopecks=19900)
|
|
assert result.amount_kopecks == 0
|
|
assert result.kind == "zero"
|
|
|
|
|
|
def test_calculate_refund_not_succeeded() -> None:
|
|
invoice = _invoice(status="pending", credits_purchased=20)
|
|
result = calculate_refund(invoice, 0, purchased_documents=20, price_per_doc_kopecks=19900)
|
|
assert result.kind == "zero"
|
|
|
|
|
|
def test_calculate_refund_outside_window() -> None:
|
|
invoice = _invoice(
|
|
credits_purchased=20,
|
|
paid_at=dt.datetime.now(tz=dt.UTC) - dt.timedelta(days=15),
|
|
)
|
|
result = calculate_refund(invoice, 0, purchased_documents=20, price_per_doc_kopecks=19900)
|
|
assert result.kind == "zero"
|
|
assert "window" in result.reason
|
|
|
|
|
|
def test_calculate_refund_zero_usage_full() -> None:
|
|
invoice = _invoice(credits_purchased=5)
|
|
result = calculate_refund(invoice, 0, purchased_documents=5, price_per_doc_kopecks=19900)
|
|
assert result.kind == "full"
|
|
|
|
|
|
def test_calculate_refund_subscription_proportional() -> None:
|
|
invoice = _invoice(kind="subscription", amount=49000, credits_purchased=None)
|
|
result = calculate_refund(invoice, 4, purchased_documents=5, price_per_doc_kopecks=19900)
|
|
assert result.kind == "zero" # 4*19900 = 79600 > 49000
|
|
|
|
|
|
def test_calculate_refund_subscription_full() -> None:
|
|
invoice = _invoice(kind="subscription", amount=49000, credits_purchased=None)
|
|
result = calculate_refund(invoice, 0, purchased_documents=5, price_per_doc_kopecks=19900)
|
|
assert result.kind == "full"
|
|
assert result.amount_kopecks == 49000
|