Kaanta / test_tax_engine.py
Eniiyanu's picture
Upload 8 files
8f0ef5f verified
"""
Test suite for NTA 2026 Tax Calculation Engine.
Validates:
- Tax config consistency
- PAYE calculations accuracy
- Progressive band computations
- Deduction calculations
- Response formatting
"""
import unittest
from datetime import date
from decimal import Decimal
from tax_config import (
get_regime, NTA_2026_CONFIG, PITA_2025_CONFIG,
TaxBand, format_bands, CIT_RATES, VAT_CONFIG
)
from paye_calculator import PAYECalculator, calculate_paye
class TestTaxConfig(unittest.TestCase):
"""Test tax configuration module."""
def test_nta_2026_regime_exists(self):
"""NTA 2026 should be the default regime."""
regime = get_regime("NTA_2026")
self.assertEqual(regime.code, "NTA_2026")
self.assertEqual(regime.name, "Nigeria Tax Act 2026")
def test_nta_2026_has_six_bands(self):
"""NTA 2026 should have 6 tax bands."""
regime = get_regime("NTA_2026")
self.assertEqual(len(regime.bands), 6)
def test_first_band_is_tax_free(self):
"""First N800,000 should be tax-free."""
regime = get_regime("NTA_2026")
first_band = regime.bands[0]
self.assertEqual(first_band.lower, 0)
self.assertEqual(first_band.upper, 800_000)
self.assertEqual(first_band.rate, 0.00)
def test_highest_band_is_25_percent(self):
"""Highest band should be 25%."""
regime = get_regime("NTA_2026")
last_band = regime.bands[-1]
self.assertEqual(last_band.rate, 0.25)
def test_rent_relief_enabled(self):
"""NTA 2026 should have rent relief enabled."""
regime = get_regime("NTA_2026")
self.assertTrue(regime.rent_relief_enabled)
self.assertEqual(regime.rent_relief_cap, 500_000)
def test_cra_disabled_in_nta_2026(self):
"""CRA should be disabled in NTA 2026."""
regime = get_regime("NTA_2026")
self.assertFalse(regime.cra_enabled)
def test_cit_rates(self):
"""CIT rates should be correctly defined."""
self.assertEqual(CIT_RATES["small"]["rate"], 0.00)
self.assertEqual(CIT_RATES["medium"]["rate"], 0.20)
self.assertEqual(CIT_RATES["large"]["rate"], 0.30)
def test_vat_rate(self):
"""VAT rate should be 7.5%."""
self.assertEqual(VAT_CONFIG["rate"], 0.075)
class TestPAYECalculator(unittest.TestCase):
"""Test PAYE calculator."""
def setUp(self):
self.calc = PAYECalculator("NTA_2026")
def test_zero_income(self):
"""Zero income should have zero tax."""
result = self.calc.calculate(gross_income=0)
self.assertEqual(result.final_tax, 0)
self.assertEqual(result.effective_rate, 0)
def test_minimum_wage_exempt(self):
"""Income at minimum wage should be exempt."""
# Annual minimum wage = 70,000 * 12 = 840,000
result = self.calc.calculate(gross_income=840_000, period="annual")
self.assertEqual(result.final_tax, 0)
def test_tax_free_first_800k(self):
"""First N800,000 taxable income should be tax-free."""
# With deductions, need higher gross to get N800k taxable
result = self.calc.calculate(gross_income=800_000, period="annual")
# After deductions, taxable < 800k, so tax should be 0
self.assertEqual(result.computed_tax, 0)
def test_progressive_taxation(self):
"""Higher income should pay progressive rates."""
low_result = self.calc.calculate(gross_income=3_000_000, period="annual")
high_result = self.calc.calculate(gross_income=30_000_000, period="annual")
# Higher income should have higher effective rate
self.assertGreater(high_result.effective_rate, low_result.effective_rate)
def test_pension_deduction(self):
"""Pension should default to 8% of gross."""
result = self.calc.calculate(gross_income=1_000_000, period="annual")
expected_pension = 1_000_000 * 0.08
self.assertEqual(result.deductions.pension_contribution, expected_pension)
def test_nhf_deduction(self):
"""NHF should default to 2.5% of gross."""
result = self.calc.calculate(gross_income=1_000_000, period="annual")
expected_nhf = 1_000_000 * 0.025
self.assertEqual(result.deductions.nhf_contribution, expected_nhf)
def test_rent_relief_capped(self):
"""Rent relief should be capped at N500,000."""
result = self.calc.calculate(
gross_income=100_000_000,
period="annual",
annual_rent_paid=10_000_000 # Would be 2M at 20%, but capped
)
self.assertEqual(result.deductions.rent_relief, 500_000)
def test_rent_relief_calculation(self):
"""Rent relief should be 20% of rent paid, up to cap."""
result = self.calc.calculate(
gross_income=10_000_000,
period="annual",
annual_rent_paid=1_000_000 # 20% = 200k, under cap
)
self.assertEqual(result.deductions.rent_relief, 200_000)
def test_tax_never_exceeds_income(self):
"""Tax should never exceed gross income."""
for income in [100_000, 1_000_000, 10_000_000, 100_000_000]:
result = self.calc.calculate(gross_income=income, period="annual")
self.assertLess(result.final_tax, result.gross_annual_income)
def test_effective_rate_below_max(self):
"""Effective rate should never exceed 25%."""
result = self.calc.calculate(gross_income=1_000_000_000, period="annual")
self.assertLess(result.effective_rate, 30) # Some margin for calculation
def test_monthly_to_annual_conversion(self):
"""Monthly calculations should convert correctly."""
monthly_result = self.calc.calculate(gross_income=500_000, period="monthly")
annual_result = self.calc.calculate(gross_income=6_000_000, period="annual")
# Should be approximately equal
self.assertAlmostEqual(
monthly_result.gross_annual_income,
annual_result.gross_annual_income,
places=0
)
class TestCalculationAccuracy(unittest.TestCase):
"""Test specific calculation scenarios for accuracy."""
def setUp(self):
self.calc = PAYECalculator("NTA_2026")
def test_500k_monthly_scenario(self):
"""Verify N500k monthly calculation."""
result = self.calc.calculate(gross_income=500_000, period="monthly")
# Gross annual = 6M
self.assertEqual(result.gross_annual_income, 6_000_000)
# Pension = 8% of 6M = 480k
self.assertEqual(result.deductions.pension_contribution, 480_000)
# NHF = 2.5% of 6M = 150k
self.assertEqual(result.deductions.nhf_contribution, 150_000)
# Taxable = 6M - 480k - 150k = 5,370,000
expected_taxable = 6_000_000 - 480_000 - 150_000
self.assertEqual(result.taxable_income, expected_taxable)
def test_band_calculations(self):
"""Verify band-by-band calculations."""
# Use a simple taxable income of exactly 3M
result = self.calc.calculate(
gross_income=3_000_000,
period="annual",
pension_contribution=0, # Override to simplify
nhf_contribution=0
)
# First 800k at 0% = 0
# 800k-3M at 15% = 2,200,000 * 0.15 = 330,000
expected_tax = 0 + (2_200_000 * 0.15)
self.assertAlmostEqual(result.computed_tax, expected_tax, places=0)
class TestOutputFormatting(unittest.TestCase):
"""Test output formatting."""
def setUp(self):
self.calc = PAYECalculator("NTA_2026")
self.result = self.calc.calculate(gross_income=500_000, period="monthly")
def test_whatsapp_format_no_emojis(self):
"""WhatsApp format should not contain emojis."""
output = self.calc.format_whatsapp(self.result)
# Common emoji codepoint ranges
emoji_patterns = ['📊', '💰', '✅', '❌', '📈', '🔴', '🟢']
for emoji in emoji_patterns:
self.assertNotIn(emoji, output)
def test_whatsapp_format_has_key_info(self):
"""WhatsApp format should contain key information."""
output = self.calc.format_whatsapp(self.result)
self.assertIn("Gross Income", output)
self.assertIn("Tax Payable", output)
self.assertIn("Take-Home", output)
self.assertIn("Kaanta", output)
def test_web_format_is_dict(self):
"""Web format should return a dictionary."""
output = self.calc.format_web(self.result)
self.assertIsInstance(output, dict)
self.assertIn("summary", output)
self.assertIn("income", output)
self.assertIn("tax", output)
def test_detailed_format_has_sections(self):
"""Detailed format should have all sections."""
output = self.calc.format_detailed(self.result)
self.assertIn("INCOME", output)
self.assertIn("DEDUCTIONS", output)
self.assertIn("TAX COMPUTATION", output)
self.assertIn("LEGAL BASIS", output)
class TestValidation(unittest.TestCase):
"""Test calculation validation."""
def setUp(self):
self.calc = PAYECalculator("NTA_2026")
def test_normal_calculation_is_valid(self):
"""Normal calculations should pass validation."""
result = self.calc.calculate(gross_income=5_000_000, period="annual")
self.assertTrue(result.validation.is_valid)
def test_confidence_score(self):
"""Confidence score should be between 0 and 1."""
result = self.calc.calculate(gross_income=5_000_000, period="annual")
self.assertGreaterEqual(result.validation.confidence, 0)
self.assertLessEqual(result.validation.confidence, 1)
if __name__ == "__main__":
unittest.main(verbosity=2)