Some checks failed
System Monitoring / Health Checks (push) Has been cancelled
System Monitoring / Performance Monitoring (push) Has been cancelled
System Monitoring / Database Monitoring (push) Has been cancelled
System Monitoring / Cache Monitoring (push) Has been cancelled
System Monitoring / Log Monitoring (push) Has been cancelled
System Monitoring / Resource Monitoring (push) Has been cancelled
System Monitoring / Uptime Monitoring (push) Has been cancelled
System Monitoring / Backup Monitoring (push) Has been cancelled
System Monitoring / Security Monitoring (push) Has been cancelled
System Monitoring / Monitoring Dashboard (push) Has been cancelled
System Monitoring / Alerting (push) Has been cancelled
Security Scanning / Dependency Scanning (push) Has been cancelled
Security Scanning / Code Security Scanning (push) Has been cancelled
Security Scanning / Secrets Scanning (push) Has been cancelled
Security Scanning / Container Security Scanning (push) Has been cancelled
Security Scanning / Compliance Checking (push) Has been cancelled
Security Scanning / Security Dashboard (push) Has been cancelled
Security Scanning / Security Remediation (push) Has been cancelled
387 lines
14 KiB
Python
387 lines
14 KiB
Python
"""
|
|
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']) |