""" Unit tests for Malaysian Validators Tests for Malaysian-specific validation utilities: - IC number validation - Phone number validation - Business registration validation - Address validation - SST calculation Author: Claude """ import pytest from django.test import TestCase from django.core.exceptions import ValidationError from backend.src.core.utils.malaysian_validators import ( validate_ic_number, validate_phone_number, validate_business_registration, validate_malaysian_address, calculate_sst, validate_postal_code, format_malaysian_phone, get_malaysian_states ) class MalaysianValidatorsTest(TestCase): """Test cases for Malaysian validators""" def test_validate_ic_number_valid(self): """Test valid Malaysian IC number validation""" valid_ic_numbers = [ '000101-01-0001', # Valid format '900101-10-1234', # Valid format '851231-12-5678', # Valid format ] for ic_number in valid_ic_numbers: result = validate_ic_number(ic_number) self.assertTrue(result['is_valid']) self.assertEqual(result['normalized'], ic_number) def test_validate_ic_number_invalid(self): """Test invalid Malaysian IC number validation""" invalid_ic_numbers = [ '123', # Too short '000101-01-000', # Wrong length '000101-01-00012', # Wrong length '000101-01-000A', # Contains letter '000101/01/0001', # Wrong separator '00-01-01-0001', # Wrong format ] for ic_number in invalid_ic_numbers: result = validate_ic_number(ic_number) self.assertFalse(result['is_valid']) self.assertIsNotNone(result.get('error')) def test_validate_phone_number_valid(self): """Test valid Malaysian phone number validation""" valid_phones = [ '+60123456789', # Standard mobile '0123456789', # Mobile without country code '+60312345678', # Landline '0312345678', # Landline without country code '+60111234567', # New mobile prefix ] for phone in valid_phones: result = validate_phone_number(phone) self.assertTrue(result['is_valid']) self.assertEqual(result['type'], 'mobile' if phone.startswith('01') else 'landline') def test_validate_phone_number_invalid(self): """Test invalid Malaysian phone number validation""" invalid_phones = [ '12345', # Too short '0123456789A', # Contains letter '+6512345678', # Singapore number '123456789012', # Too long '0112345678', # Invalid prefix ] for phone in invalid_phones: result = validate_phone_number(phone) self.assertFalse(result['is_valid']) self.assertIsNotNone(result.get('error')) def test_validate_business_registration_valid(self): """Test valid business registration validation""" valid_registrations = [ '202401000001', # Company registration '001234567-K', # Business registration 'SM1234567-K', # Small medium enterprise ] for reg in valid_registrations: result = validate_business_registration(reg) self.assertTrue(result['is_valid']) self.assertIsNotNone(result.get('type')) def test_validate_business_registration_invalid(self): """Test invalid business registration validation""" invalid_registrations = [ '123', # Too short '20240100000', # Missing check digit '202401000001A', # Contains letter '0012345678-K', # Too long ] for reg in invalid_registrations: result = validate_business_registration(reg) self.assertFalse(result['is_valid']) self.assertIsNotNone(result.get('error')) def test_validate_malaysian_address_valid(self): """Test valid Malaysian address validation""" valid_addresses = [ { 'address': '123 Test Street', 'city': 'Kuala Lumpur', 'state': 'KUL', 'postal_code': '50000' }, { 'address': '456 Jalan Merdeka', 'city': 'Penang', 'state': 'PNG', 'postal_code': '10000' } ] for address in valid_addresses: result = validate_malaysian_address(address) self.assertTrue(result['is_valid']) def test_validate_malaysian_address_invalid(self): """Test invalid Malaysian address validation""" invalid_addresses = [ { 'address': '', # Empty address 'city': 'Kuala Lumpur', 'state': 'KUL', 'postal_code': '50000' }, { 'address': '123 Test Street', 'city': '', # Empty city 'state': 'KUL', 'postal_code': '50000' }, { 'address': '123 Test Street', 'city': 'Kuala Lumpur', 'state': 'XX', # Invalid state 'postal_code': '50000' }, { 'address': '123 Test Street', 'city': 'Kuala Lumpur', 'state': 'KUL', 'postal_code': '123' # Invalid postal code } ] for address in invalid_addresses: result = validate_malaysian_address(address) self.assertFalse(result['is_valid']) self.assertIsNotNone(result.get('errors')) def test_calculate_sst(self): """Test SST calculation""" test_cases = [ {'amount': 100.00, 'expected_sst': 6.00}, # 6% SST {'amount': 50.00, 'expected_sst': 3.00}, # 6% SST {'amount': 0.00, 'expected_sst': 0.00}, # Zero amount {'amount': 999.99, 'expected_sst': 59.9994}, # High amount ] for case in test_cases: sst_amount = calculate_sst(case['amount']) self.assertAlmostEqual(sst_amount, case['expected_sst'], places=4) def test_calculate_sst_invalid(self): """Test SST calculation with invalid inputs""" invalid_cases = [ -100.00, # Negative amount None, # None value 'invalid', # String value ] for amount in invalid_cases: with self.assertRaises(Exception): calculate_sst(amount) def test_validate_postal_code_valid(self): """Test valid postal code validation""" valid_postal_codes = [ '50000', # KL postal code '10000', # Penang postal code '80000', # Johor Bahru postal code '97000', # Sarawak postal code ] for postal_code in valid_postal_codes: result = validate_postal_code(postal_code) self.assertTrue(result['is_valid']) self.assertEqual(result['state'], result.get('state')) def test_validate_postal_code_invalid(self): """Test invalid postal code validation""" invalid_postal_codes = [ '1234', # Too short '123456', # Too long 'ABCDE', # Contains letters '00000', # Invalid range '99999', # Invalid range ] for postal_code in invalid_postal_codes: result = validate_postal_code(postal_code) self.assertFalse(result['is_valid']) self.assertIsNotNone(result.get('error')) def test_format_malaysian_phone(self): """Test Malaysian phone number formatting""" test_cases = [ {'input': '0123456789', 'expected': '+6012-3456789'}, {'input': '+60123456789', 'expected': '+6012-3456789'}, {'input': '0312345678', 'expected': '+603-12345678'}, {'input': '+60312345678', 'expected': '+603-12345678'}, ] for case in test_cases: formatted = format_malaysian_phone(case['input']) self.assertEqual(formatted, case['expected']) def test_format_malaysian_phone_invalid(self): """Test formatting invalid phone numbers""" invalid_phones = [ '12345', # Too short 'invalid', # Non-numeric '6512345678', # Singapore number ] for phone in invalid_phones: result = format_malaysian_phone(phone) self.assertEqual(result, phone) # Should return original if invalid def test_get_malaysian_states(self): """Test getting Malaysian states""" states = get_malaysian_states() # Check if all expected states are present expected_states = [ 'Johor', 'Kedah', 'Kelantan', 'Malacca', 'Negeri Sembilan', 'Pahang', 'Perak', 'Perlis', 'Penang', 'Sabah', 'Sarawak', 'Selangor', 'Terengganu', 'Kuala Lumpur', 'Labuan', 'Putrajaya' ] for state in expected_states: self.assertIn(state, states) # Check state codes self.assertEqual(states['Kuala Lumpur'], 'KUL') self.assertEqual(states['Penang'], 'PNG') self.assertEqual(states['Johor'], 'JHR') def test_ic_number_structure_validation(self): """Test IC number structure validation""" # Test age calculation from IC ic_1990 = '900101-01-0001' # Born 1990 result = validate_ic_number(ic_1990) self.assertTrue(result['is_valid']) self.assertEqual(result['birth_year'], 1990) self.assertEqual(result['birth_date'], '1990-01-01') # Test gender from IC (last digit: odd = male, even = female) ic_male = '900101-01-0001' # Odd last digit ic_female = '900101-01-0002' # Even last digit result_male = validate_ic_number(ic_male) result_female = validate_ic_number(ic_female) self.assertEqual(result_male['gender'], 'male') self.assertEqual(result_female['gender'], 'female') def test_phone_number_type_detection(self): """Test phone number type detection""" mobile_numbers = [ '0123456789', # Maxis '0198765432', # Celcom '0162345678', # DiGi '0181234567', # U Mobile '01112345678', # Yes 4G ] landline_numbers = [ '0312345678', # KL '0412345678', # Penang '0512345678', # Perak '0612345678', # Melaka '0712345678', # Johor ] for number in mobile_numbers: result = validate_phone_number(number) self.assertTrue(result['is_valid']) self.assertEqual(result['type'], 'mobile') for number in landline_numbers: result = validate_phone_number(number) self.assertTrue(result['is_valid']) self.assertEqual(result['type'], 'landline') def test_business_registration_type_detection(self): """Test business registration type detection""" company_reg = '202401000001' # Company registration business_reg = '001234567-K' # Business registration sme_reg = 'SM1234567-K' # Small medium enterprise result_company = validate_business_registration(company_reg) result_business = validate_business_registration(business_reg) result_sme = validate_business_registration(sme_reg) self.assertEqual(result_company['type'], 'company') self.assertEqual(result_business['type'], 'business') self.assertEqual(result_sme['type'], 'sme') def test_address_component_validation(self): """Test individual address component validation""" # Test state code validation valid_states = ['KUL', 'PNG', 'JHR', 'KDH', 'KTN'] invalid_states = ['XX', 'ABC', '123'] for state in valid_states: address = { 'address': '123 Test Street', 'city': 'Test City', 'state': state, 'postal_code': '50000' } result = validate_malaysian_address(address) self.assertTrue(result['is_valid']) for state in invalid_states: address = { 'address': '123 Test Street', 'city': 'Test City', 'state': state, 'postal_code': '50000' } result = validate_malaysian_address(address) self.assertFalse(result['is_valid']) def test_sst_edge_cases(self): """Test SST calculation edge cases""" # Test very small amounts sst_small = calculate_sst(0.01) self.assertAlmostEqual(sst_small, 0.0006, places=4) # Test very large amounts sst_large = calculate_sst(1000000.00) self.assertEqual(sst_large, 60000.00) # Test decimal places sst_decimal = calculate_sst(123.45) self.assertAlmostEqual(sst_decimal, 7.407, places=4) def test_postal_code_state_mapping(self): """Test postal code to state mapping""" # Test known postal code ranges test_cases = [ {'postal_code': '50000', 'expected_state': 'KUL'}, # KL {'postal_code': '10000', 'expected_state': 'PNG'}, # Penang {'postal_code': '80000', 'expected_state': 'JHR'}, # Johor {'postal_code': '09000', 'expected_state': 'KDH'}, # Kedah {'postal_code': '98000', 'expected_state': 'SBH'}, # Sabah ] for case in test_cases: result = validate_postal_code(case['postal_code']) self.assertTrue(result['is_valid']) self.assertEqual(result['state'], case['expected_state'])