fire-planner/tests/test_tax_base.py
2026-05-07 17:06:19 +00:00

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