""" Unit tests for Beauty Models Tests for beauty module models: - Client - Service Author: Claude """ import pytest from django.test import TestCase from django.contrib.auth import get_user_model from django.utils import timezone from decimal import Decimal from datetime import date, time, timedelta from backend.src.core.models.tenant import Tenant from backend.src.core.models.user import User from backend.src.modules.beauty.models.client import Client from backend.src.modules.beauty.models.service import Service User = get_user_model() class ClientModelTest(TestCase): """Test cases for Client model""" def setUp(self): self.tenant = Tenant.objects.create( name='Test Beauty Salon', schema_name='test_beauty', domain='testbeauty.com', business_type='beauty' ) self.user = User.objects.create_user( username='receptionist', email='receptionist@test.com', password='test123', tenant=self.tenant, role='staff' ) self.client_data = { 'tenant': self.tenant, 'client_number': 'C2024010001', 'first_name': 'Siti', 'last_name': 'Binti Ahmad', 'ic_number': '000101-01-0001', 'passport_number': '', 'nationality': 'Malaysian', 'gender': 'female', 'date_of_birth': date(1995, 1, 1), 'email': 'siti.client@test.com', 'phone': '+60123456789', 'whatsapp_number': '+60123456789', 'emergency_contact_name': 'Ahmad Bin Ibrahim', 'emergency_contact_phone': '+60123456788', 'emergency_contact_relationship': 'Husband', 'address': '123 Beauty Street', 'city': 'Kuala Lumpur', 'state': 'KUL', 'postal_code': '50000', 'occupation': 'Office Worker', 'company': 'Test Company', 'skin_type': 'normal', 'hair_type': 'straight', 'allergies': 'None', 'skin_conditions': 'None', 'medications': 'None', 'pregnancy_status': False, 'pregnancy_due_date': None, 'breastfeeding': False, 'preferred_services': ['facial', 'manicure'], 'membership_tier': 'basic', 'loyalty_points': 0, 'total_spent': Decimal('0.00'), 'visit_count': 0, 'last_visit_date': None, 'preferred_stylist': '', 'preferred_appointment_time': 'morning', 'marketing_consent': True, 'sms_consent': True, 'email_consent': True, 'photo_consent': False, 'medical_consent': True, 'privacy_consent': True, 'notes': 'New client', 'referral_source': 'walk_in', 'referred_by': '', 'is_active': True, 'created_by': self.user } def test_create_client(self): """Test creating a new client""" client = Client.objects.create(**self.client_data) self.assertEqual(client.tenant, self.tenant) self.assertEqual(client.client_number, self.client_data['client_number']) self.assertEqual(client.first_name, self.client_data['first_name']) self.assertEqual(client.last_name, self.client_data['last_name']) self.assertEqual(client.ic_number, self.client_data['ic_number']) self.assertEqual(client.gender, self.client_data['gender']) self.assertEqual(client.skin_type, self.client_data['skin_type']) self.assertEqual(client.membership_tier, self.client_data['membership_tier']) self.assertEqual(client.loyalty_points, self.client_data['loyalty_points']) self.assertTrue(client.is_active) def test_client_string_representation(self): """Test client string representation""" client = Client.objects.create(**self.client_data) self.assertEqual(str(client), f"{client.first_name} {client.last_name} ({client.client_number})") def test_client_full_name(self): """Test client full name property""" client = Client.objects.create(**self.client_data) self.assertEqual(client.full_name, f"{client.first_name} {client.last_name}") def test_client_age(self): """Test client age calculation""" client = Client.objects.create(**self.client_data) # Age should be calculated based on date of birth today = date.today() expected_age = today.year - client.date_of_birth.year if today.month < client.date_of_birth.month or (today.month == client.date_of_birth.month and today.day < client.date_of_birth.day): expected_age -= 1 self.assertEqual(client.age, expected_age) def test_client_malaysian_ic_validation(self): """Test Malaysian IC number validation""" # Valid IC number client = Client.objects.create(**self.client_data) self.assertEqual(client.ic_number, self.client_data['ic_number']) # Invalid IC number format invalid_data = self.client_data.copy() invalid_data['ic_number'] = '123' with self.assertRaises(Exception): Client.objects.create(**invalid_data) def test_client_gender_choices(self): """Test client gender validation""" invalid_data = self.client_data.copy() invalid_data['gender'] = 'invalid_gender' with self.assertRaises(Exception): Client.objects.create(**invalid_data) def test_client_membership_tier_choices(self): """Test client membership tier validation""" invalid_data = self.client_data.copy() invalid_data['membership_tier'] = 'invalid_tier' with self.assertRaises(Exception): Client.objects.create(**invalid_data) def test_client_skin_type_choices(self): """Test client skin type validation""" invalid_data = self.client_data.copy() invalid_data['skin_type'] = 'invalid_skin' with self.assertRaises(Exception): Client.objects.create(**invalid_data) def test_client_hair_type_choices(self): """Test client hair type validation""" invalid_data = self.client_data.copy() invalid_data['hair_type'] = 'invalid_hair' with self.assertRaises(Exception): Client.objects.create(**invalid_data) def test_client_phone_validation(self): """Test Malaysian phone number validation""" # Valid Malaysian phone numbers client = Client.objects.create(**self.client_data) self.assertEqual(client.phone, self.client_data['phone']) self.assertEqual(client.whatsapp_number, self.client_data['whatsapp_number']) # Invalid phone invalid_data = self.client_data.copy() invalid_data['phone'] = '12345' with self.assertRaises(Exception): Client.objects.create(**invalid_data) def test_client_medical_information(self): """Test client medical information validation""" client = Client.objects.create(**self.client_data) self.assertEqual(client.allergies, self.client_data['allergies']) self.assertEqual(client.skin_conditions, self.client_data['skin_conditions']) self.assertEqual(client.medications, self.client_data['medications']) self.assertFalse(client.pregnancy_status) self.assertFalse(client.breastfeeding) def test_client_consent_preferences(self): """Test client consent preferences""" client = Client.objects.create(**self.client_data) self.assertTrue(client.marketing_consent) self.assertTrue(client.sms_consent) self.assertTrue(client.email_consent) self.assertFalse(client.photo_consent) self.assertTrue(client.medical_consent) self.assertTrue(client.privacy_consent) def test_client_loyalty_program(self): """Test client loyalty program features""" client = Client.objects.create(**self.client_data) self.assertEqual(client.loyalty_points, 0) self.assertEqual(client.total_spent, Decimal('0.00')) self.assertEqual(client.visit_count, 0) # Test tier progression logic self.assertEqual(client.membership_tier, 'basic') def test_client_referral_source_choices(self): """Test client referral source validation""" # Test valid referral sources valid_sources = ['walk_in', 'friend', 'social_media', 'advertisement', 'online', 'other'] for source in valid_sources: data = self.client_data.copy() data['referral_source'] = source client = Client.objects.create(**data) self.assertEqual(client.referral_source, source) # Test invalid referral source invalid_data = self.client_data.copy() invalid_data['referral_source'] = 'invalid_source' with self.assertRaises(Exception): Client.objects.create(**invalid_data) class ServiceModelTest(TestCase): """Test cases for Service model""" def setUp(self): self.tenant = Tenant.objects.create( name='Test Beauty Salon', schema_name='test_beauty', domain='testbeauty.com', business_type='beauty' ) self.user = User.objects.create_user( username='manager', email='manager@test.com', password='test123', tenant=self.tenant, role='admin' ) self.service_data = { 'tenant': self.tenant, 'service_code': 'FAC-BASIC-001', 'name': 'Basic Facial Treatment', 'description': 'A basic facial treatment for all skin types', 'service_category': 'facial', 'duration': 60, # minutes 'base_price': Decimal('80.00'), 'premium_price': Decimal('120.00'), 'vip_price': Decimal('100.00'), 'tax_rate': 6.0, # SST 'is_taxable': True, 'commission_rate': 20.0, # percentage 'difficulty_level': 'basic', 'experience_required': 0, # years 'min_age': 16, 'max_age': 65, 'suitable_for_skin_types': ['normal', 'dry', 'oily', 'combination', 'sensitive'], 'suitable_for_hair_types': [], 'pregnancy_safe': True, 'breastfeeding_safe': True, 'requires_patch_test': False, 'has_contraindications': False, 'contraindications': '', 'equipment_required': ['Facial steamer', 'Cleansing brush', 'Toner'], 'products_used': ['Cleanser', 'Toner', 'Moisturizer', 'Sunscreen'], 'steps': ['Cleansing', 'Exfoliation', 'Massage', 'Mask', 'Moisturizing'], 'aftercare_instructions': 'Avoid direct sunlight for 24 hours', 'frequency_limit_days': 7, 'is_active': True, 'is_popular': True, 'is_new': False, 'is_promotional': False, 'kkm_approval_required': False, 'kkm_approval_number': '', 'min_booking_notice_hours': 2, 'cancellation_policy_hours': 24, 'late_arrival_policy_minutes': 15, 'no_show_policy': 'fee', 'created_by': self.user } def test_create_service(self): """Test creating a new service""" service = Service.objects.create(**self.service_data) self.assertEqual(service.tenant, self.tenant) self.assertEqual(service.service_code, self.service_data['service_code']) self.assertEqual(service.name, self.service_data['name']) self.assertEqual(service.service_category, self.service_data['service_category']) self.assertEqual(service.duration, self.service_data['duration']) self.assertEqual(service.base_price, self.service_data['base_price']) self.assertEqual(service.tax_rate, self.service_data['tax_rate']) self.assertEqual(service.difficulty_level, self.service_data['difficulty_level']) self.assertTrue(service.is_active) self.assertTrue(service.is_popular) def test_service_string_representation(self): """Test service string representation""" service = Service.objects.create(**self.service_data) self.assertEqual(str(service), service.name) def test_service_price_with_tax(self): """Test service price calculation with tax""" service = Service.objects.create(**self.service_data) # Base price with tax expected_base_with_tax = service.base_price * (1 + service.tax_rate / 100) self.assertEqual(service.base_price_with_tax, expected_base_with_tax) # Premium price with tax expected_premium_with_tax = service.premium_price * (1 + service.tax_rate / 100) self.assertEqual(service.premium_price_with_tax, expected_premium_with_tax) # VIP price with tax expected_vip_with_tax = service.vip_price * (1 + service.tax_rate / 100) self.assertEqual(service.vip_price_with_tax, expected_vip_with_tax) def test_service_category_choices(self): """Test service category validation""" invalid_data = self.service_data.copy() invalid_data['service_category'] = 'invalid_category' with self.assertRaises(Exception): Service.objects.create(**invalid_data) def test_service_difficulty_level_choices(self): """Test service difficulty level validation""" invalid_data = self.service_data.copy() invalid_data['difficulty_level'] = 'invalid_difficulty' with self.assertRaises(Exception): Service.objects.create(**invalid_data) def test_service_tax_calculation(self): """Test service tax calculation""" service = Service.objects.create(**self.service_data) # Tax amount for base price expected_tax = service.base_price * (service.tax_rate / 100) self.assertEqual(service.tax_amount, expected_tax) def test_service_commission_calculation(self): """Test service commission calculation""" service = Service.objects.create(**self.service_data) # Commission amount for base price expected_commission = service.base_price * (service.commission_rate / 100) self.assertEqual(service.commission_amount, expected_commission) def test_service_age_validation(self): """Test service age validation""" # Valid age range service = Service.objects.create(**self.service_data) self.assertEqual(service.min_age, self.service_data['min_age']) self.assertEqual(service.max_age, self.service_data['max_age']) # Invalid age range (min > max) invalid_data = self.service_data.copy() invalid_data['min_age'] = 30 invalid_data['max_age'] = 20 with self.assertRaises(Exception): Service.objects.create(**invalid_data) def test_service_malaysian_sst_validation(self): """Test Malaysian SST validation""" # Valid SST rate service = Service.objects.create(**self.service_data) self.assertEqual(service.tax_rate, 6.0) # Standard SST rate # Invalid SST rate (negative) invalid_data = self.service_data.copy() invalid_data['tax_rate'] = -1.0 with self.assertRaises(Exception): Service.objects.create(**invalid_data) def test_service_duration_validation(self): """Test service duration validation""" # Valid duration service = Service.objects.create(**self.service_data) self.assertEqual(service.duration, self.service_data['duration']) # Invalid duration (too short) invalid_data = self.service_data.copy() invalid_data['duration'] = 0 with self.assertRaises(Exception): Service.objects.create(**invalid_data) def test_service_price_validation(self): """Test service price validation""" # Valid prices service = Service.objects.create(**self.service_data) self.assertEqual(service.base_price, self.service_data['base_price']) self.assertEqual(service.premium_price, self.service_data['premium_price']) self.assertEqual(service.vip_price, self.service_data['vip_price']) # Invalid price (negative) invalid_data = self.service_data.copy() invalid_data['base_price'] = Decimal('-10.00') with self.assertRaises(Exception): Service.objects.create(**invalid_data) def test_service_suitability_validation(self): """Test service suitability validation""" service = Service.objects.create(**self.service_data) # Check skin type suitability self.assertIn('normal', service.suitable_for_skin_types) self.assertIn('sensitive', service.suitable_for_skin_types) # Check pregnancy safety self.assertTrue(service.pregnancy_safe) self.assertTrue(service.breastfeeding_safe) def test_service_malaysian_beauty_regulations(self): """Test Malaysian beauty industry regulations""" service = Service.objects.create(**self.service_data) self.assertEqual(service.tax_rate, 6.0) # SST compliance self.assertFalse(service.kkm_approval_required) # KKM approval status # Test service requiring KKM approval data = self.service_data.copy() data['name'] = 'Advanced Laser Treatment' data['kkm_approval_required'] = True data['kkm_approval_number'] = 'KKM/2024/001234' service_kkm = Service.objects.create(**data) self.assertTrue(service_kkm.kkm_approval_required) self.assertEqual(service_kkm.kkm_approval_number, data['kkm_approval_number']) def test_service_booking_policies(self): """Test service booking policies""" service = Service.objects.create(**self.service_data) self.assertEqual(service.min_booking_notice_hours, 2) self.assertEqual(service.cancellation_policy_hours, 24) self.assertEqual(service.late_arrival_policy_minutes, 15) self.assertEqual(service.no_show_policy, 'fee')