"""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