151 lines
5 KiB
Python
151 lines
5 KiB
Python
|
|
"""Nomad, Malaysia, Thailand, Cyprus, Bulgaria, UAE regimes."""
|
|||
|
|
from decimal import Decimal
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
|
|||
|
|
from fire_planner.tax.base import TaxInputs, TaxRegime
|
|||
|
|
from fire_planner.tax.bulgaria import BulgariaTaxRegime
|
|||
|
|
from fire_planner.tax.cyprus import CyprusTaxRegime
|
|||
|
|
from fire_planner.tax.malaysia import MalaysiaTaxRegime
|
|||
|
|
from fire_planner.tax.nomad import NomadTaxRegime
|
|||
|
|
from fire_planner.tax.thailand import ThailandTaxRegime
|
|||
|
|
from fire_planner.tax.uae import UaeTaxRegime
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_nomad_zero_inputs() -> None:
|
|||
|
|
assert NomadTaxRegime().compute_tax(TaxInputs()).total == Decimal("0")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_nomad_one_pc_premium() -> None:
|
|||
|
|
b = NomadTaxRegime().compute_tax(
|
|||
|
|
TaxInputs(capital_gains=Decimal("100000"), dividends=Decimal("20000")))
|
|||
|
|
assert b.other == Decimal("1200")
|
|||
|
|
assert b.total == Decimal("1200")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_nomad_isa_excluded_from_premium() -> None:
|
|||
|
|
b = NomadTaxRegime().compute_tax(TaxInputs(isa_withdrawals=Decimal("100000")))
|
|||
|
|
assert b.total == Decimal("0")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_malaysia_zero_on_foreign_income() -> None:
|
|||
|
|
b = MalaysiaTaxRegime().compute_tax(
|
|||
|
|
TaxInputs(capital_gains=Decimal("500000"), dividends=Decimal("50000")))
|
|||
|
|
assert b.total == Decimal("0")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_thailand_zero_on_foreign_income() -> None:
|
|||
|
|
b = ThailandTaxRegime().compute_tax(
|
|||
|
|
TaxInputs(capital_gains=Decimal("500000"), dividends=Decimal("50000")))
|
|||
|
|
assert b.total == Decimal("0")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_cyprus_gesy_below_cap() -> None:
|
|||
|
|
# £100k chargeable, below €180k cap (~£154,800 default)
|
|||
|
|
# 2.65% × £100,000 = £2,650
|
|||
|
|
b = CyprusTaxRegime().compute_tax(TaxInputs(dividends=Decimal("100000")))
|
|||
|
|
assert b.healthcare_levy == Decimal("2650.0000")
|
|||
|
|
assert b.income_tax == Decimal("0")
|
|||
|
|
assert b.capital_gains_tax == Decimal("0")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_cyprus_gesy_above_cap() -> None:
|
|||
|
|
# £200k chargeable; cap GBP = £154,800 (€180k × 0.86)
|
|||
|
|
# 2.65% × £154,800 = £4,102.20
|
|||
|
|
b = CyprusTaxRegime().compute_tax(TaxInputs(dividends=Decimal("200000")))
|
|||
|
|
assert b.healthcare_levy == Decimal("4102.2000")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_cyprus_custom_fx() -> None:
|
|||
|
|
# Cap = 180,000 × 0.90 = 162,000
|
|||
|
|
b = CyprusTaxRegime(gbp_per_eur=Decimal("0.90")).compute_tax(
|
|||
|
|
TaxInputs(dividends=Decimal("200000")))
|
|||
|
|
assert b.healthcare_levy == Decimal("4293.0000")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_uae_zero_on_all_personal_income() -> None:
|
|||
|
|
"""UAE has 0% PIT — capital gains, dividends, earned income all 0."""
|
|||
|
|
b = UaeTaxRegime().compute_tax(
|
|||
|
|
TaxInputs(
|
|||
|
|
earned_income=Decimal("60000"),
|
|||
|
|
capital_gains=Decimal("500000"),
|
|||
|
|
dividends=Decimal("80000"),
|
|||
|
|
interest=Decimal("5000"),
|
|||
|
|
))
|
|||
|
|
assert b.total == Decimal("0")
|
|||
|
|
assert b.income_tax == Decimal("0")
|
|||
|
|
assert b.capital_gains_tax == Decimal("0")
|
|||
|
|
assert b.dividend_tax == Decimal("0")
|
|||
|
|
assert b.healthcare_levy == Decimal("0")
|
|||
|
|
assert b.other == Decimal("0")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_uae_no_regulatory_premium() -> None:
|
|||
|
|
"""Unlike NomadTaxRegime, UAE charges no premium — it's a real
|
|||
|
|
tax residence with a treaty network."""
|
|||
|
|
b = UaeTaxRegime().compute_tax(TaxInputs(capital_gains=Decimal("100000")))
|
|||
|
|
assert b.total == Decimal("0")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_uae_zero_inputs() -> None:
|
|||
|
|
assert UaeTaxRegime().compute_tax(TaxInputs()).total == Decimal("0")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_bulgaria_flat_10_pc() -> None:
|
|||
|
|
b = BulgariaTaxRegime().compute_tax(
|
|||
|
|
TaxInputs(
|
|||
|
|
earned_income=Decimal("50000"),
|
|||
|
|
capital_gains=Decimal("30000"),
|
|||
|
|
dividends=Decimal("10000"),
|
|||
|
|
))
|
|||
|
|
assert b.income_tax == Decimal("5000.00")
|
|||
|
|
assert b.capital_gains_tax == Decimal("3000.00")
|
|||
|
|
assert b.dividend_tax == Decimal("1000.00")
|
|||
|
|
assert b.total == Decimal("9000.00")
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.parametrize("regime", [
|
|||
|
|
NomadTaxRegime(),
|
|||
|
|
MalaysiaTaxRegime(),
|
|||
|
|
ThailandTaxRegime(),
|
|||
|
|
CyprusTaxRegime(),
|
|||
|
|
BulgariaTaxRegime(),
|
|||
|
|
UaeTaxRegime(),
|
|||
|
|
])
|
|||
|
|
def test_total_equals_sum(regime: TaxRegime) -> None:
|
|||
|
|
inputs = TaxInputs(
|
|||
|
|
earned_income=Decimal("60000"),
|
|||
|
|
capital_gains=Decimal("15000"),
|
|||
|
|
dividends=Decimal("8000"),
|
|||
|
|
interest=Decimal("500"),
|
|||
|
|
)
|
|||
|
|
b = regime.compute_tax(inputs)
|
|||
|
|
assert (b.total == b.income_tax + b.national_insurance + b.capital_gains_tax + b.dividend_tax +
|
|||
|
|
b.healthcare_levy + b.other)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.parametrize("regime", [
|
|||
|
|
NomadTaxRegime(),
|
|||
|
|
MalaysiaTaxRegime(),
|
|||
|
|
ThailandTaxRegime(),
|
|||
|
|
CyprusTaxRegime(),
|
|||
|
|
BulgariaTaxRegime(),
|
|||
|
|
UaeTaxRegime(),
|
|||
|
|
])
|
|||
|
|
def test_each_regime_has_a_name(regime: TaxRegime) -> None:
|
|||
|
|
assert regime.name
|
|||
|
|
assert isinstance(regime.name, str)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.parametrize("regime", [
|
|||
|
|
BulgariaTaxRegime(),
|
|||
|
|
NomadTaxRegime(),
|
|||
|
|
CyprusTaxRegime(),
|
|||
|
|
])
|
|||
|
|
def test_lower_spend_lower_tax(regime: TaxRegime) -> None:
|
|||
|
|
"""Sanity: more chargeable income → never less tax (for the
|
|||
|
|
regimes that actually charge)."""
|
|||
|
|
less = regime.compute_tax(TaxInputs(dividends=Decimal("10000")))
|
|||
|
|
more = regime.compute_tax(TaxInputs(dividends=Decimal("100000")))
|
|||
|
|
assert more.total >= less.total
|