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
459 lines
18 KiB
Python
459 lines
18 KiB
Python
"""
|
|
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') |