70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
"""Bracket-arithmetic and breakdown invariants."""
|
|
from decimal import Decimal
|
|
|
|
from hypothesis import given
|
|
from hypothesis import strategies as st
|
|
|
|
from fire_planner.tax.base import TaxBreakdown, TaxInputs, apply_brackets
|
|
|
|
|
|
def test_apply_brackets_zero_input() -> None:
|
|
assert apply_brackets(Decimal("0"), [(Decimal("100"), Decimal("0.2"))]) == Decimal("0")
|
|
|
|
|
|
def test_apply_brackets_negative_input() -> None:
|
|
# Negative income shouldn't generate a refund — clamp to zero.
|
|
assert apply_brackets(Decimal("-1000"), [(Decimal("100"), Decimal("0.2"))]) == Decimal("0")
|
|
|
|
|
|
def test_apply_brackets_within_first_band() -> None:
|
|
brackets = [(Decimal("100"), Decimal("0.2")), (Decimal("Infinity"), Decimal("0.4"))]
|
|
assert apply_brackets(Decimal("50"), brackets) == Decimal("10")
|
|
|
|
|
|
def test_apply_brackets_spans_two_bands() -> None:
|
|
# 100 @ 20% = 20; next 50 @ 40% = 20 → total 40
|
|
brackets = [(Decimal("100"), Decimal("0.2")), (Decimal("Infinity"), Decimal("0.4"))]
|
|
assert apply_brackets(Decimal("150"), brackets) == Decimal("40")
|
|
|
|
|
|
def test_apply_brackets_uk_paye_2026_smoke() -> None:
|
|
# Taxable income £80,000 (gross £92,570 less £12,570 PA):
|
|
# £37,700 @ 20% = £7,540
|
|
# £42,300 @ 40% = £16,920
|
|
# total = £24,460
|
|
brackets = [
|
|
(Decimal("37700"), Decimal("0.20")),
|
|
(Decimal("112570"), Decimal("0.40")),
|
|
(Decimal("Infinity"), Decimal("0.45")),
|
|
]
|
|
assert apply_brackets(Decimal("80000"), brackets) == Decimal("24460")
|
|
|
|
|
|
@given(amount=st.decimals(min_value=0, max_value=10_000_000, allow_nan=False, allow_infinity=False))
|
|
def test_apply_brackets_monotone_in_amount(amount: Decimal) -> None:
|
|
"""More taxable income → never less tax."""
|
|
brackets = [
|
|
(Decimal("37700"), Decimal("0.20")),
|
|
(Decimal("112570"), Decimal("0.40")),
|
|
(Decimal("Infinity"), Decimal("0.45")),
|
|
]
|
|
extra = Decimal("100")
|
|
assert apply_brackets(amount + extra, brackets) >= apply_brackets(amount, brackets)
|
|
|
|
|
|
def test_breakdown_total_is_sum_of_components() -> None:
|
|
b = TaxBreakdown(
|
|
income_tax=Decimal("10000"),
|
|
national_insurance=Decimal("3000"),
|
|
capital_gains_tax=Decimal("500"),
|
|
dividend_tax=Decimal("200"),
|
|
healthcare_levy=Decimal("100"),
|
|
other=Decimal("50"),
|
|
)
|
|
assert b.total == Decimal("13850")
|
|
|
|
|
|
def test_inputs_default_to_zero() -> None:
|
|
i = TaxInputs()
|
|
assert i.earned_income == Decimal("0")
|
|
assert i.years_since_uk_departure == 0
|