project initialization
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
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
This commit is contained in:
0
backend/tests/unit/models/__init__.py
Normal file
0
backend/tests/unit/models/__init__.py
Normal file
459
backend/tests/unit/models/test_beauty_models.py
Normal file
459
backend/tests/unit/models/test_beauty_models.py
Normal file
@@ -0,0 +1,459 @@
|
||||
"""
|
||||
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')
|
||||
340
backend/tests/unit/models/test_core_models.py
Normal file
340
backend/tests/unit/models/test_core_models.py
Normal file
@@ -0,0 +1,340 @@
|
||||
"""
|
||||
Unit tests for Core Models
|
||||
|
||||
Tests for all core models:
|
||||
- Tenant
|
||||
- User
|
||||
- Subscription
|
||||
- Module
|
||||
- PaymentTransaction
|
||||
|
||||
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, timedelta
|
||||
|
||||
from backend.src.core.models.tenant import Tenant
|
||||
from backend.src.core.models.user import User
|
||||
from backend.src.core.models.subscription import Subscription
|
||||
from backend.src.core.models.module import Module
|
||||
from backend.src.core.models.payment import PaymentTransaction
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class TenantModelTest(TestCase):
|
||||
"""Test cases for Tenant model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant_data = {
|
||||
'name': 'Test Business Sdn Bhd',
|
||||
'schema_name': 'test_business',
|
||||
'domain': 'testbusiness.com',
|
||||
'business_type': 'retail',
|
||||
'registration_number': '202401000001',
|
||||
'tax_id': 'MY123456789',
|
||||
'contact_email': 'contact@testbusiness.com',
|
||||
'contact_phone': '+60123456789',
|
||||
'address': '123 Test Street, Kuala Lumpur',
|
||||
'city': 'Kuala Lumpur',
|
||||
'state': 'KUL',
|
||||
'postal_code': '50000',
|
||||
'country': 'Malaysia',
|
||||
'is_active': True
|
||||
}
|
||||
|
||||
def test_create_tenant(self):
|
||||
"""Test creating a new tenant"""
|
||||
tenant = Tenant.objects.create(**self.tenant_data)
|
||||
self.assertEqual(tenant.name, self.tenant_data['name'])
|
||||
self.assertEqual(tenant.schema_name, self.tenant_data['schema_name'])
|
||||
self.assertEqual(tenant.business_type, self.tenant_data['business_type'])
|
||||
self.assertTrue(tenant.is_active)
|
||||
self.assertEqual(tenant.subscription_tier, 'free')
|
||||
self.assertIsNotNone(tenant.created_at)
|
||||
|
||||
def test_tenant_string_representation(self):
|
||||
"""Test tenant string representation"""
|
||||
tenant = Tenant.objects.create(**self.tenant_data)
|
||||
self.assertEqual(str(tenant), f"{tenant.name} ({tenant.schema_name})")
|
||||
|
||||
def test_tenant_business_type_choices(self):
|
||||
"""Test tenant business type validation"""
|
||||
invalid_data = self.tenant_data.copy()
|
||||
invalid_data['business_type'] = 'invalid_type'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Tenant.objects.create(**invalid_data)
|
||||
|
||||
def test_tenant_malaysian_business_validation(self):
|
||||
"""Test Malaysian business registration validation"""
|
||||
# Valid registration number
|
||||
tenant = Tenant.objects.create(**self.tenant_data)
|
||||
self.assertEqual(tenant.registration_number, self.tenant_data['registration_number'])
|
||||
|
||||
# Invalid registration number format
|
||||
invalid_data = self.tenant_data.copy()
|
||||
invalid_data['registration_number'] = '123'
|
||||
with self.assertRaises(Exception):
|
||||
Tenant.objects.create(**invalid_data)
|
||||
|
||||
def test_tenant_phone_validation(self):
|
||||
"""Test Malaysian phone number validation"""
|
||||
# Valid Malaysian phone number
|
||||
tenant = Tenant.objects.create(**self.tenant_data)
|
||||
self.assertEqual(tenant.contact_phone, self.tenant_data['contact_phone'])
|
||||
|
||||
# Invalid phone number
|
||||
invalid_data = self.tenant_data.copy()
|
||||
invalid_data['contact_phone'] = '12345'
|
||||
with self.assertRaises(Exception):
|
||||
Tenant.objects.create(**invalid_data)
|
||||
|
||||
|
||||
class UserModelTest(TestCase):
|
||||
"""Test cases for User model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Business Sdn Bhd',
|
||||
schema_name='test_business',
|
||||
domain='testbusiness.com',
|
||||
business_type='retail'
|
||||
)
|
||||
|
||||
self.user_data = {
|
||||
'username': 'testuser',
|
||||
'email': 'user@test.com',
|
||||
'first_name': 'Test',
|
||||
'last_name': 'User',
|
||||
'phone': '+60123456789',
|
||||
'ic_number': '000101-01-0001',
|
||||
'tenant': self.tenant,
|
||||
'role': 'owner',
|
||||
'is_active': True
|
||||
}
|
||||
|
||||
def test_create_user(self):
|
||||
"""Test creating a new user"""
|
||||
user = User.objects.create_user(**self.user_data)
|
||||
self.assertEqual(user.username, self.user_data['username'])
|
||||
self.assertEqual(user.email, self.user_data['email'])
|
||||
self.assertEqual(user.tenant, self.tenant)
|
||||
self.assertEqual(user.role, self.user_data['role'])
|
||||
self.assertTrue(user.is_active)
|
||||
self.assertFalse(user.is_staff)
|
||||
|
||||
def test_create_superuser(self):
|
||||
"""Test creating a superuser"""
|
||||
superuser = User.objects.create_superuser(
|
||||
username='admin',
|
||||
email='admin@test.com',
|
||||
password='admin123'
|
||||
)
|
||||
self.assertTrue(superuser.is_staff)
|
||||
self.assertTrue(superuser.is_superuser)
|
||||
self.assertEqual(superuser.role, 'admin')
|
||||
|
||||
def test_user_string_representation(self):
|
||||
"""Test user string representation"""
|
||||
user = User.objects.create_user(**self.user_data)
|
||||
self.assertEqual(str(user), user.email)
|
||||
|
||||
def test_user_full_name(self):
|
||||
"""Test user full name property"""
|
||||
user = User.objects.create_user(**self.user_data)
|
||||
self.assertEqual(user.full_name, f"{user.first_name} {user.last_name}")
|
||||
|
||||
def test_user_malaysian_ic_validation(self):
|
||||
"""Test Malaysian IC number validation"""
|
||||
# Valid IC number
|
||||
user = User.objects.create_user(**self.user_data)
|
||||
self.assertEqual(user.ic_number, self.user_data['ic_number'])
|
||||
|
||||
# Invalid IC number
|
||||
invalid_data = self.user_data.copy()
|
||||
invalid_data['ic_number'] = '123'
|
||||
with self.assertRaises(Exception):
|
||||
User.objects.create_user(**invalid_data)
|
||||
|
||||
def test_user_role_choices(self):
|
||||
"""Test user role validation"""
|
||||
invalid_data = self.user_data.copy()
|
||||
invalid_data['role'] = 'invalid_role'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
User.objects.create_user(**invalid_data)
|
||||
|
||||
|
||||
class SubscriptionModelTest(TestCase):
|
||||
"""Test cases for Subscription model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Business Sdn Bhd',
|
||||
schema_name='test_business',
|
||||
domain='testbusiness.com',
|
||||
business_type='retail'
|
||||
)
|
||||
|
||||
self.subscription_data = {
|
||||
'tenant': self.tenant,
|
||||
'plan': 'premium',
|
||||
'status': 'active',
|
||||
'start_date': date.today(),
|
||||
'end_date': date.today() + timedelta(days=30),
|
||||
'amount': Decimal('299.00'),
|
||||
'currency': 'MYR',
|
||||
'billing_cycle': 'monthly',
|
||||
'auto_renew': True
|
||||
}
|
||||
|
||||
def test_create_subscription(self):
|
||||
"""Test creating a new subscription"""
|
||||
subscription = Subscription.objects.create(**self.subscription_data)
|
||||
self.assertEqual(subscription.tenant, self.tenant)
|
||||
self.assertEqual(subscription.plan, self.subscription_data['plan'])
|
||||
self.assertEqual(subscription.status, self.subscription_data['status'])
|
||||
self.assertEqual(subscription.amount, self.subscription_data['amount'])
|
||||
self.assertTrue(subscription.auto_renew)
|
||||
|
||||
def test_subscription_string_representation(self):
|
||||
"""Test subscription string representation"""
|
||||
subscription = Subscription.objects.create(**self.subscription_data)
|
||||
expected = f"{self.tenant.name} - Premium ({subscription.status})"
|
||||
self.assertEqual(str(subscription), expected)
|
||||
|
||||
def test_subscription_is_active_property(self):
|
||||
"""Test subscription is_active property"""
|
||||
# Active subscription
|
||||
subscription = Subscription.objects.create(**self.subscription_data)
|
||||
self.assertTrue(subscription.is_active)
|
||||
|
||||
# Expired subscription
|
||||
subscription.end_date = date.today() - timedelta(days=1)
|
||||
subscription.save()
|
||||
self.assertFalse(subscription.is_active)
|
||||
|
||||
# Cancelled subscription
|
||||
subscription.status = 'cancelled'
|
||||
subscription.end_date = date.today() + timedelta(days=30)
|
||||
subscription.save()
|
||||
self.assertFalse(subscription.is_active)
|
||||
|
||||
def test_subscription_status_choices(self):
|
||||
"""Test subscription status validation"""
|
||||
invalid_data = self.subscription_data.copy()
|
||||
invalid_data['status'] = 'invalid_status'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Subscription.objects.create(**invalid_data)
|
||||
|
||||
|
||||
class ModuleModelTest(TestCase):
|
||||
"""Test cases for Module model"""
|
||||
|
||||
def setUp(self):
|
||||
self.module_data = {
|
||||
'name': 'Retail Management',
|
||||
'code': 'retail',
|
||||
'description': 'Complete retail management solution',
|
||||
'category': 'industry',
|
||||
'version': '1.0.0',
|
||||
'is_active': True,
|
||||
'is_core': False,
|
||||
'dependencies': ['core'],
|
||||
'config_schema': {'features': ['inventory', 'sales']},
|
||||
'pricing_tier': 'premium'
|
||||
}
|
||||
|
||||
def test_create_module(self):
|
||||
"""Test creating a new module"""
|
||||
module = Module.objects.create(**self.module_data)
|
||||
self.assertEqual(module.name, self.module_data['name'])
|
||||
self.assertEqual(module.code, self.module_data['code'])
|
||||
self.assertEqual(module.category, self.module_data['category'])
|
||||
self.assertTrue(module.is_active)
|
||||
self.assertFalse(module.is_core)
|
||||
|
||||
def test_module_string_representation(self):
|
||||
"""Test module string representation"""
|
||||
module = Module.objects.create(**self.module_data)
|
||||
self.assertEqual(str(module), module.name)
|
||||
|
||||
def test_module_category_choices(self):
|
||||
"""Test module category validation"""
|
||||
invalid_data = self.module_data.copy()
|
||||
invalid_data['category'] = 'invalid_category'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Module.objects.create(**invalid_data)
|
||||
|
||||
|
||||
class PaymentTransactionModelTest(TestCase):
|
||||
"""Test cases for PaymentTransaction model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Business Sdn Bhd',
|
||||
schema_name='test_business',
|
||||
domain='testbusiness.com',
|
||||
business_type='retail'
|
||||
)
|
||||
|
||||
self.subscription = Subscription.objects.create(
|
||||
tenant=self.tenant,
|
||||
plan='premium',
|
||||
status='active',
|
||||
start_date=date.today(),
|
||||
end_date=date.today() + timedelta(days=30),
|
||||
amount=Decimal('299.00'),
|
||||
currency='MYR'
|
||||
)
|
||||
|
||||
self.payment_data = {
|
||||
'tenant': self.tenant,
|
||||
'subscription': self.subscription,
|
||||
'transaction_id': 'PAY-2024010001',
|
||||
'amount': Decimal('299.00'),
|
||||
'currency': 'MYR',
|
||||
'payment_method': 'fpx',
|
||||
'status': 'completed',
|
||||
'payment_date': timezone.now(),
|
||||
'description': 'Monthly subscription payment'
|
||||
}
|
||||
|
||||
def test_create_payment_transaction(self):
|
||||
"""Test creating a new payment transaction"""
|
||||
payment = PaymentTransaction.objects.create(**self.payment_data)
|
||||
self.assertEqual(payment.tenant, self.tenant)
|
||||
self.assertEqual(payment.subscription, self.subscription)
|
||||
self.assertEqual(payment.transaction_id, self.payment_data['transaction_id'])
|
||||
self.assertEqual(payment.amount, self.payment_data['amount'])
|
||||
self.assertEqual(payment.status, self.payment_data['status'])
|
||||
|
||||
def test_payment_string_representation(self):
|
||||
"""Test payment transaction string representation"""
|
||||
payment = PaymentTransaction.objects.create(**self.payment_data)
|
||||
expected = f"PAY-2024010001 - RM299.00 ({payment.status})"
|
||||
self.assertEqual(str(payment), expected)
|
||||
|
||||
def test_payment_method_choices(self):
|
||||
"""Test payment method validation"""
|
||||
invalid_data = self.payment_data.copy()
|
||||
invalid_data['payment_method'] = 'invalid_method'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
PaymentTransaction.objects.create(**invalid_data)
|
||||
|
||||
def test_payment_status_choices(self):
|
||||
"""Test payment status validation"""
|
||||
invalid_data = self.payment_data.copy()
|
||||
invalid_data['status'] = 'invalid_status'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
PaymentTransaction.objects.create(**invalid_data)
|
||||
413
backend/tests/unit/models/test_education_models.py
Normal file
413
backend/tests/unit/models/test_education_models.py
Normal file
@@ -0,0 +1,413 @@
|
||||
"""
|
||||
Unit tests for Education Models
|
||||
|
||||
Tests for education module models:
|
||||
- Student
|
||||
- Class
|
||||
|
||||
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.education.models.student import Student
|
||||
from backend.src.modules.education.models.class_model import Class
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class StudentModelTest(TestCase):
|
||||
"""Test cases for Student model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Education Center',
|
||||
schema_name='test_education',
|
||||
domain='testeducation.com',
|
||||
business_type='education'
|
||||
)
|
||||
|
||||
self.user = User.objects.create_user(
|
||||
username='admin',
|
||||
email='admin@test.com',
|
||||
password='test123',
|
||||
tenant=self.tenant,
|
||||
role='admin'
|
||||
)
|
||||
|
||||
self.student_data = {
|
||||
'tenant': self.tenant,
|
||||
'student_id': 'S2024010001',
|
||||
'first_name': 'Ahmad',
|
||||
'last_name': 'Bin Ibrahim',
|
||||
'ic_number': '000101-01-0001',
|
||||
'gender': 'male',
|
||||
'date_of_birth': date(2010, 1, 1),
|
||||
'nationality': 'Malaysian',
|
||||
'religion': 'Islam',
|
||||
'race': 'Malay',
|
||||
'email': 'ahmad.student@test.com',
|
||||
'phone': '+60123456789',
|
||||
'address': '123 Student Street',
|
||||
'city': 'Kuala Lumpur',
|
||||
'state': 'KUL',
|
||||
'postal_code': '50000',
|
||||
'father_name': 'Ibrahim Bin Ali',
|
||||
'father_phone': '+60123456788',
|
||||
'father_occupation': 'Engineer',
|
||||
'mother_name': 'Aminah Binti Ahmad',
|
||||
'mother_phone': '+60123456787',
|
||||
'mother_occupation': 'Teacher',
|
||||
'emergency_contact_name': 'Ibrahim Bin Ali',
|
||||
'emergency_contact_phone': '+60123456788',
|
||||
'emergency_contact_relationship': 'Father',
|
||||
'previous_school': 'SK Test Primary',
|
||||
'previous_grade': '6A',
|
||||
'current_grade': 'Form 1',
|
||||
'stream': 'science',
|
||||
'admission_date': date.today(),
|
||||
'graduation_date': None,
|
||||
'status': 'active',
|
||||
'medical_conditions': 'None',
|
||||
'allergies': 'None',
|
||||
'special_needs': 'None',
|
||||
'is_active': True,
|
||||
'created_by': self.user
|
||||
}
|
||||
|
||||
def test_create_student(self):
|
||||
"""Test creating a new student"""
|
||||
student = Student.objects.create(**self.student_data)
|
||||
self.assertEqual(student.tenant, self.tenant)
|
||||
self.assertEqual(student.student_id, self.student_data['student_id'])
|
||||
self.assertEqual(student.first_name, self.student_data['first_name'])
|
||||
self.assertEqual(student.last_name, self.student_data['last_name'])
|
||||
self.assertEqual(student.ic_number, self.student_data['ic_number'])
|
||||
self.assertEqual(student.gender, self.student_data['gender'])
|
||||
self.assertEqual(student.current_grade, self.student_data['current_grade'])
|
||||
self.assertEqual(student.stream, self.student_data['stream'])
|
||||
self.assertEqual(student.status, self.student_data['status'])
|
||||
self.assertTrue(student.is_active)
|
||||
|
||||
def test_student_string_representation(self):
|
||||
"""Test student string representation"""
|
||||
student = Student.objects.create(**self.student_data)
|
||||
self.assertEqual(str(student), f"{student.first_name} {student.last_name} ({student.student_id})")
|
||||
|
||||
def test_student_full_name(self):
|
||||
"""Test student full name property"""
|
||||
student = Student.objects.create(**self.student_data)
|
||||
self.assertEqual(student.full_name, f"{student.first_name} {student.last_name}")
|
||||
|
||||
def test_student_age(self):
|
||||
"""Test student age calculation"""
|
||||
student = Student.objects.create(**self.student_data)
|
||||
|
||||
# Age should be calculated based on date of birth
|
||||
today = date.today()
|
||||
expected_age = today.year - student.date_of_birth.year
|
||||
if today.month < student.date_of_birth.month or (today.month == student.date_of_birth.month and today.day < student.date_of_birth.day):
|
||||
expected_age -= 1
|
||||
|
||||
self.assertEqual(student.age, expected_age)
|
||||
|
||||
def test_student_malaysian_ic_validation(self):
|
||||
"""Test Malaysian IC number validation"""
|
||||
# Valid IC number
|
||||
student = Student.objects.create(**self.student_data)
|
||||
self.assertEqual(student.ic_number, self.student_data['ic_number'])
|
||||
|
||||
# Invalid IC number format
|
||||
invalid_data = self.student_data.copy()
|
||||
invalid_data['ic_number'] = '123'
|
||||
with self.assertRaises(Exception):
|
||||
Student.objects.create(**invalid_data)
|
||||
|
||||
def test_student_gender_choices(self):
|
||||
"""Test student gender validation"""
|
||||
invalid_data = self.student_data.copy()
|
||||
invalid_data['gender'] = 'invalid_gender'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Student.objects.create(**invalid_data)
|
||||
|
||||
def test_student_grade_validation(self):
|
||||
"""Test student grade validation"""
|
||||
# Test valid grades
|
||||
valid_grades = ['Form 1', 'Form 2', 'Form 3', 'Form 4', 'Form 5', 'Form 6']
|
||||
for grade in valid_grades:
|
||||
data = self.student_data.copy()
|
||||
data['current_grade'] = grade
|
||||
student = Student.objects.create(**data)
|
||||
self.assertEqual(student.current_grade, grade)
|
||||
|
||||
# Test invalid grade
|
||||
invalid_data = self.student_data.copy()
|
||||
invalid_data['current_grade'] = 'Form 7'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Student.objects.create(**invalid_data)
|
||||
|
||||
def test_student_stream_choices(self):
|
||||
"""Test student stream validation"""
|
||||
# Test valid streams
|
||||
valid_streams = ['science', 'arts', 'commerce', 'technical']
|
||||
for stream in valid_streams:
|
||||
data = self.student_data.copy()
|
||||
data['stream'] = stream
|
||||
student = Student.objects.create(**data)
|
||||
self.assertEqual(student.stream, stream)
|
||||
|
||||
# Test invalid stream
|
||||
invalid_data = self.student_data.copy()
|
||||
invalid_data['stream'] = 'invalid_stream'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Student.objects.create(**invalid_data)
|
||||
|
||||
def test_student_status_choices(self):
|
||||
"""Test student status validation"""
|
||||
# Test valid statuses
|
||||
valid_statuses = ['active', 'inactive', 'graduated', 'transferred', 'suspended']
|
||||
for status in valid_statuses:
|
||||
data = self.student_data.copy()
|
||||
data['status'] = status
|
||||
student = Student.objects.create(**data)
|
||||
self.assertEqual(student.status, status)
|
||||
|
||||
# Test invalid status
|
||||
invalid_data = self.student_data.copy()
|
||||
invalid_data['status'] = 'invalid_status'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Student.objects.create(**invalid_data)
|
||||
|
||||
def test_student_parent_information(self):
|
||||
"""Test student parent information validation"""
|
||||
student = Student.objects.create(**self.student_data)
|
||||
|
||||
self.assertEqual(student.father_name, self.student_data['father_name'])
|
||||
self.assertEqual(student.mother_name, self.student_data['mother_name'])
|
||||
self.assertEqual(student.emergency_contact_name, self.student_data['emergency_contact_name'])
|
||||
|
||||
def test_student_malaysian_education_info(self):
|
||||
"""Test Malaysian education specific information"""
|
||||
student = Student.objects.create(**self.student_data)
|
||||
|
||||
self.assertEqual(student.religion, self.student_data['religion'])
|
||||
self.assertEqual(student.race, self.student_data['race'])
|
||||
self.assertEqual(student.previous_school, self.student_data['previous_school'])
|
||||
self.assertEqual(student.previous_grade, self.student_data['previous_grade'])
|
||||
|
||||
|
||||
class ClassModelTest(TestCase):
|
||||
"""Test cases for Class model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Education Center',
|
||||
schema_name='test_education',
|
||||
domain='testeducation.com',
|
||||
business_type='education'
|
||||
)
|
||||
|
||||
self.teacher = User.objects.create_user(
|
||||
username='teacher',
|
||||
email='teacher@test.com',
|
||||
password='test123',
|
||||
tenant=self.tenant,
|
||||
role='staff'
|
||||
)
|
||||
|
||||
self.student = Student.objects.create(
|
||||
tenant=self.tenant,
|
||||
student_id='S2024010001',
|
||||
first_name='Ahmad',
|
||||
last_name='Bin Ibrahim',
|
||||
ic_number='000101-01-0001',
|
||||
gender='male',
|
||||
date_of_birth=date(2010, 1, 1),
|
||||
current_grade='Form 1',
|
||||
stream='science',
|
||||
admission_date=date.today(),
|
||||
status='active'
|
||||
)
|
||||
|
||||
self.class_data = {
|
||||
'tenant': self.tenant,
|
||||
'class_name': 'Mathematics Form 1',
|
||||
'class_code': 'MATH-F1-2024',
|
||||
'grade': 'Form 1',
|
||||
'stream': 'science',
|
||||
'subject': 'Mathematics',
|
||||
'academic_year': '2024',
|
||||
'semester': '1',
|
||||
'teacher': self.teacher,
|
||||
'room': 'B1-01',
|
||||
'max_students': 30,
|
||||
'schedule_days': ['Monday', 'Wednesday', 'Friday'],
|
||||
'start_time': time(8, 0),
|
||||
'end_time': time(9, 30),
|
||||
'start_date': date.today(),
|
||||
'end_date': date.today() + timedelta(days=180),
|
||||
'is_active': True,
|
||||
'syllabus': 'KSSM Mathematics Form 1',
|
||||
'objectives': 'Complete KSSM Mathematics syllabus',
|
||||
'assessment_methods': 'Tests, Assignments, Projects',
|
||||
'created_by': self.teacher
|
||||
}
|
||||
|
||||
def test_create_class(self):
|
||||
"""Test creating a new class"""
|
||||
class_obj = Class.objects.create(**self.class_data)
|
||||
self.assertEqual(class_obj.tenant, self.tenant)
|
||||
self.assertEqual(class_obj.class_name, self.class_data['class_name'])
|
||||
self.assertEqual(class_obj.class_code, self.class_data['class_code'])
|
||||
self.assertEqual(class_obj.grade, self.class_data['grade'])
|
||||
self.assertEqual(class_obj.stream, self.class_data['stream'])
|
||||
self.assertEqual(class_obj.subject, self.class_data['subject'])
|
||||
self.assertEqual(class_obj.teacher, self.teacher)
|
||||
self.assertEqual(class_obj.max_students, self.class_data['max_students'])
|
||||
self.assertTrue(class_obj.is_active)
|
||||
|
||||
def test_class_string_representation(self):
|
||||
"""Test class string representation"""
|
||||
class_obj = Class.objects.create(**self.class_data)
|
||||
self.assertEqual(str(class_obj), f"{class_obj.class_name} ({class_obj.class_code})")
|
||||
|
||||
def test_class_duration(self):
|
||||
"""Test class duration calculation"""
|
||||
class_obj = Class.objects.create(**self.class_data)
|
||||
|
||||
# Duration should be 90 minutes
|
||||
self.assertEqual(class_obj.duration, 90)
|
||||
|
||||
def test_class_grade_validation(self):
|
||||
"""Test class grade validation"""
|
||||
# Test valid grades
|
||||
valid_grades = ['Form 1', 'Form 2', 'Form 3', 'Form 4', 'Form 5', 'Form 6']
|
||||
for grade in valid_grades:
|
||||
data = self.class_data.copy()
|
||||
data['grade'] = grade
|
||||
class_obj = Class.objects.create(**data)
|
||||
self.assertEqual(class_obj.grade, grade)
|
||||
|
||||
# Test invalid grade
|
||||
invalid_data = self.class_data.copy()
|
||||
invalid_data['grade'] = 'Form 7'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Class.objects.create(**invalid_data)
|
||||
|
||||
def test_class_stream_choices(self):
|
||||
"""Test class stream validation"""
|
||||
# Test valid streams
|
||||
valid_streams = ['science', 'arts', 'commerce', 'technical']
|
||||
for stream in valid_streams:
|
||||
data = self.class_data.copy()
|
||||
data['stream'] = stream
|
||||
class_obj = Class.objects.create(**data)
|
||||
self.assertEqual(class_obj.stream, stream)
|
||||
|
||||
# Test invalid stream
|
||||
invalid_data = self.class_data.copy()
|
||||
invalid_data['stream'] = 'invalid_stream'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Class.objects.create(**invalid_data)
|
||||
|
||||
def test_class_semester_choices(self):
|
||||
"""Test class semester validation"""
|
||||
# Test valid semesters
|
||||
valid_semesters = ['1', '2']
|
||||
for semester in valid_semesters:
|
||||
data = self.class_data.copy()
|
||||
data['semester'] = semester
|
||||
class_obj = Class.objects.create(**data)
|
||||
self.assertEqual(class_obj.semester, semester)
|
||||
|
||||
# Test invalid semester
|
||||
invalid_data = self.class_data.copy()
|
||||
invalid_data['semester'] = '3'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Class.objects.create(**invalid_data)
|
||||
|
||||
def test_class_schedule_validation(self):
|
||||
"""Test class schedule validation"""
|
||||
# Valid schedule
|
||||
class_obj = Class.objects.create(**self.class_data)
|
||||
self.assertEqual(class_obj.schedule_days, self.class_data['schedule_days'])
|
||||
self.assertEqual(class_obj.start_time, self.class_data['start_time'])
|
||||
self.assertEqual(class_obj.end_time, self.class_data['end_time'])
|
||||
|
||||
# Invalid time range (end before start)
|
||||
invalid_data = self.class_data.copy()
|
||||
invalid_data['start_time'] = time(10, 0)
|
||||
invalid_data['end_time'] = time(9, 30)
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Class.objects.create(**invalid_data)
|
||||
|
||||
def test_class_student_enrollment(self):
|
||||
"""Test class student enrollment"""
|
||||
class_obj = Class.objects.create(**self.class_data)
|
||||
|
||||
# Add student to class
|
||||
class_obj.students.add(self.student)
|
||||
|
||||
self.assertIn(self.student, class_obj.students.all())
|
||||
self.assertEqual(class_obj.students.count(), 1)
|
||||
|
||||
def test_class_capacity_validation(self):
|
||||
"""Test class capacity validation"""
|
||||
class_obj = Class.objects.create(**self.class_data)
|
||||
|
||||
# Test capacity
|
||||
self.assertEqual(class_obj.max_students, 30)
|
||||
|
||||
# Test is_full method
|
||||
self.assertFalse(class_obj.is_full)
|
||||
|
||||
# Add students up to capacity
|
||||
for i in range(30):
|
||||
student_data = self.student.__dict__.copy()
|
||||
student_data['student_id'] = f'S202401{i:04d}'
|
||||
student_data['first_name'] = f'Student{i}'
|
||||
student_data.pop('id', None)
|
||||
student_data.pop('_state', None)
|
||||
|
||||
student = Student.objects.create(**student_data)
|
||||
class_obj.students.add(student)
|
||||
|
||||
# Should be full now
|
||||
self.assertTrue(class_obj.is_full)
|
||||
|
||||
def test_class_malaysian_education_features(self):
|
||||
"""Test Malaysian education specific features"""
|
||||
class_obj = Class.objects.create(**self.class_data)
|
||||
|
||||
self.assertEqual(class_obj.subject, self.class_data['subject'])
|
||||
self.assertEqual(class_obj.academic_year, self.class_data['academic_year'])
|
||||
self.assertEqual(class_obj.syllabus, self.class_data['syllabus'])
|
||||
|
||||
def test_class_date_validation(self):
|
||||
"""Test class date validation"""
|
||||
# Valid date range
|
||||
class_obj = Class.objects.create(**self.class_data)
|
||||
self.assertLessEqual(class_obj.start_date, class_obj.end_date)
|
||||
|
||||
# Invalid date range (end before start)
|
||||
invalid_data = self.class_data.copy()
|
||||
invalid_data['start_date'] = date.today()
|
||||
invalid_data['end_date'] = date.today() - timedelta(days=1)
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Class.objects.create(**invalid_data)
|
||||
323
backend/tests/unit/models/test_healthcare_models.py
Normal file
323
backend/tests/unit/models/test_healthcare_models.py
Normal file
@@ -0,0 +1,323 @@
|
||||
"""
|
||||
Unit tests for Healthcare Models
|
||||
|
||||
Tests for healthcare module models:
|
||||
- Patient
|
||||
- Appointment
|
||||
|
||||
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.healthcare.models.patient import Patient
|
||||
from backend.src.modules.healthcare.models.appointment import Appointment
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class PatientModelTest(TestCase):
|
||||
"""Test cases for Patient model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Healthcare Sdn Bhd',
|
||||
schema_name='test_healthcare',
|
||||
domain='testhealthcare.com',
|
||||
business_type='healthcare'
|
||||
)
|
||||
|
||||
self.user = User.objects.create_user(
|
||||
username='doctor',
|
||||
email='doctor@test.com',
|
||||
password='test123',
|
||||
tenant=self.tenant,
|
||||
role='staff'
|
||||
)
|
||||
|
||||
self.patient_data = {
|
||||
'tenant': self.tenant,
|
||||
'patient_id': 'P2024010001',
|
||||
'first_name': 'John',
|
||||
'last_name': 'Doe',
|
||||
'ic_number': '000101-01-0001',
|
||||
'passport_number': '',
|
||||
'nationality': 'Malaysian',
|
||||
'gender': 'male',
|
||||
'date_of_birth': date(1990, 1, 1),
|
||||
'blood_type': 'O+',
|
||||
'email': 'john.doe@test.com',
|
||||
'phone': '+60123456789',
|
||||
'emergency_contact_name': 'Jane Doe',
|
||||
'emergency_contact_phone': '+60123456788',
|
||||
'emergency_contact_relationship': 'Spouse',
|
||||
'address': '123 Test Street',
|
||||
'city': 'Kuala Lumpur',
|
||||
'state': 'KUL',
|
||||
'postal_code': '50000',
|
||||
'medical_history': 'No significant medical history',
|
||||
'allergies': 'None known',
|
||||
'current_medications': 'None',
|
||||
'chronic_conditions': 'None',
|
||||
'last_visit_date': None,
|
||||
'is_active': True,
|
||||
'created_by': self.user
|
||||
}
|
||||
|
||||
def test_create_patient(self):
|
||||
"""Test creating a new patient"""
|
||||
patient = Patient.objects.create(**self.patient_data)
|
||||
self.assertEqual(patient.tenant, self.tenant)
|
||||
self.assertEqual(patient.patient_id, self.patient_data['patient_id'])
|
||||
self.assertEqual(patient.first_name, self.patient_data['first_name'])
|
||||
self.assertEqual(patient.last_name, self.patient_data['last_name'])
|
||||
self.assertEqual(patient.ic_number, self.patient_data['ic_number'])
|
||||
self.assertEqual(patient.gender, self.patient_data['gender'])
|
||||
self.assertEqual(patient.blood_type, self.patient_data['blood_type'])
|
||||
self.assertTrue(patient.is_active)
|
||||
|
||||
def test_patient_string_representation(self):
|
||||
"""Test patient string representation"""
|
||||
patient = Patient.objects.create(**self.patient_data)
|
||||
self.assertEqual(str(patient), f"{patient.first_name} {patient.last_name} ({patient.patient_id})")
|
||||
|
||||
def test_patient_full_name(self):
|
||||
"""Test patient full name property"""
|
||||
patient = Patient.objects.create(**self.patient_data)
|
||||
self.assertEqual(patient.full_name, f"{patient.first_name} {patient.last_name}")
|
||||
|
||||
def test_patient_age(self):
|
||||
"""Test patient age calculation"""
|
||||
patient = Patient.objects.create(**self.patient_data)
|
||||
|
||||
# Age should be calculated based on date of birth
|
||||
today = date.today()
|
||||
expected_age = today.year - patient.date_of_birth.year
|
||||
if today.month < patient.date_of_birth.month or (today.month == patient.date_of_birth.month and today.day < patient.date_of_birth.day):
|
||||
expected_age -= 1
|
||||
|
||||
self.assertEqual(patient.age, expected_age)
|
||||
|
||||
def test_patient_malaysian_ic_validation(self):
|
||||
"""Test Malaysian IC number validation"""
|
||||
# Valid IC number
|
||||
patient = Patient.objects.create(**self.patient_data)
|
||||
self.assertEqual(patient.ic_number, self.patient_data['ic_number'])
|
||||
|
||||
# Invalid IC number format
|
||||
invalid_data = self.patient_data.copy()
|
||||
invalid_data['ic_number'] = '123'
|
||||
with self.assertRaises(Exception):
|
||||
Patient.objects.create(**invalid_data)
|
||||
|
||||
def test_patient_gender_choices(self):
|
||||
"""Test patient gender validation"""
|
||||
invalid_data = self.patient_data.copy()
|
||||
invalid_data['gender'] = 'invalid_gender'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Patient.objects.create(**invalid_data)
|
||||
|
||||
def test_patient_blood_type_choices(self):
|
||||
"""Test patient blood type validation"""
|
||||
invalid_data = self.patient_data.copy()
|
||||
invalid_data['blood_type'] = 'Z+'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Patient.objects.create(**invalid_data)
|
||||
|
||||
def test_patient_phone_validation(self):
|
||||
"""Test Malaysian phone number validation"""
|
||||
# Valid Malaysian phone number
|
||||
patient = Patient.objects.create(**self.patient_data)
|
||||
self.assertEqual(patient.phone, self.patient_data['phone'])
|
||||
|
||||
# Invalid phone number
|
||||
invalid_data = self.patient_data.copy()
|
||||
invalid_data['phone'] = '12345'
|
||||
with self.assertRaises(Exception):
|
||||
Patient.objects.create(**invalid_data)
|
||||
|
||||
def test_patient_medical_info_validation(self):
|
||||
"""Test patient medical information validation"""
|
||||
# Test with medical conditions
|
||||
data = self.patient_data.copy()
|
||||
data['chronic_conditions'] = 'Diabetes, Hypertension'
|
||||
data['allergies'] = 'Penicillin, Sulfa drugs'
|
||||
|
||||
patient = Patient.objects.create(**data)
|
||||
self.assertEqual(patient.chronic_conditions, 'Diabetes, Hypertension')
|
||||
self.assertEqual(patient.allergies, 'Penicillin, Sulfa drugs')
|
||||
|
||||
|
||||
class AppointmentModelTest(TestCase):
|
||||
"""Test cases for Appointment model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Healthcare Sdn Bhd',
|
||||
schema_name='test_healthcare',
|
||||
domain='testhealthcare.com',
|
||||
business_type='healthcare'
|
||||
)
|
||||
|
||||
self.doctor = User.objects.create_user(
|
||||
username='doctor',
|
||||
email='doctor@test.com',
|
||||
password='test123',
|
||||
tenant=self.tenant,
|
||||
role='staff'
|
||||
)
|
||||
|
||||
self.patient = Patient.objects.create(
|
||||
tenant=self.tenant,
|
||||
patient_id='P2024010001',
|
||||
first_name='John',
|
||||
last_name='Doe',
|
||||
ic_number='000101-01-0001',
|
||||
gender='male',
|
||||
date_of_birth=date(1990, 1, 1),
|
||||
blood_type='O+',
|
||||
phone='+60123456789',
|
||||
created_by=self.doctor
|
||||
)
|
||||
|
||||
self.appointment_data = {
|
||||
'tenant': self.tenant,
|
||||
'patient': self.patient,
|
||||
'doctor': self.doctor,
|
||||
'appointment_number': 'APT-2024010001',
|
||||
'appointment_date': date.today() + timedelta(days=1),
|
||||
'appointment_time': time(10, 0),
|
||||
'end_time': time(10, 30),
|
||||
'appointment_type': 'consultation',
|
||||
'status': 'scheduled',
|
||||
'reason': 'General checkup',
|
||||
'notes': '',
|
||||
'is_telemedicine': False,
|
||||
'telemedicine_link': '',
|
||||
'reminder_sent': False,
|
||||
'created_by': self.doctor
|
||||
}
|
||||
|
||||
def test_create_appointment(self):
|
||||
"""Test creating a new appointment"""
|
||||
appointment = Appointment.objects.create(**self.appointment_data)
|
||||
self.assertEqual(appointment.tenant, self.tenant)
|
||||
self.assertEqual(appointment.patient, self.patient)
|
||||
self.assertEqual(appointment.doctor, self.doctor)
|
||||
self.assertEqual(appointment.appointment_number, self.appointment_data['appointment_number'])
|
||||
self.assertEqual(appointment.status, self.appointment_data['status'])
|
||||
self.assertEqual(appointment.appointment_type, self.appointment_data['appointment_type'])
|
||||
self.assertFalse(appointment.is_telemedicine)
|
||||
|
||||
def test_appointment_string_representation(self):
|
||||
"""Test appointment string representation"""
|
||||
appointment = Appointment.objects.create(**self.appointment_data)
|
||||
expected = f"{self.patient.full_name} - {appointment.appointment_date} at {appointment.appointment_time}"
|
||||
self.assertEqual(str(appointment), expected)
|
||||
|
||||
def test_appointment_duration(self):
|
||||
"""Test appointment duration calculation"""
|
||||
appointment = Appointment.objects.create(**self.appointment_data)
|
||||
|
||||
# Duration should be 30 minutes
|
||||
self.assertEqual(appointment.duration, 30)
|
||||
|
||||
def test_appointment_is_upcoming(self):
|
||||
"""Test appointment upcoming status"""
|
||||
# Future appointment
|
||||
appointment = Appointment.objects.create(**self.appointment_data)
|
||||
self.assertTrue(appointment.is_upcoming)
|
||||
|
||||
# Past appointment
|
||||
appointment.appointment_date = date.today() - timedelta(days=1)
|
||||
appointment.save()
|
||||
self.assertFalse(appointment.is_upcoming)
|
||||
|
||||
def test_appointment_status_choices(self):
|
||||
"""Test appointment status validation"""
|
||||
invalid_data = self.appointment_data.copy()
|
||||
invalid_data['status'] = 'invalid_status'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Appointment.objects.create(**invalid_data)
|
||||
|
||||
def test_appointment_type_choices(self):
|
||||
"""Test appointment type validation"""
|
||||
invalid_data = self.appointment_data.copy()
|
||||
invalid_data['appointment_type'] = 'invalid_type'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Appointment.objects.create(**invalid_data)
|
||||
|
||||
def test_appointment_time_validation(self):
|
||||
"""Test appointment time validation"""
|
||||
# Valid time range
|
||||
appointment = Appointment.objects.create(**self.appointment_data)
|
||||
self.assertEqual(appointment.appointment_time, self.appointment_data['appointment_time'])
|
||||
self.assertEqual(appointment.end_time, self.appointment_data['end_time'])
|
||||
|
||||
# Invalid time range (end before start)
|
||||
invalid_data = self.appointment_data.copy()
|
||||
invalid_data['appointment_time'] = time(11, 0)
|
||||
invalid_data['end_time'] = time(10, 30)
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Appointment.objects.create(**invalid_data)
|
||||
|
||||
def test_appointment_conflict_detection(self):
|
||||
"""Test appointment conflict detection"""
|
||||
# Create first appointment
|
||||
appointment1 = Appointment.objects.create(**self.appointment_data)
|
||||
|
||||
# Try to create conflicting appointment
|
||||
conflict_data = self.appointment_data.copy()
|
||||
conflict_data['appointment_number'] = 'APT-2024010002'
|
||||
conflict_data['appointment_time'] = time(10, 15)
|
||||
conflict_data['end_time'] = time(10, 45)
|
||||
|
||||
# This should not raise an exception but conflict detection should be available
|
||||
appointment2 = Appointment.objects.create(**conflict_data)
|
||||
|
||||
# Check if there's a conflict
|
||||
self.assertTrue(
|
||||
appointment1.appointment_date == appointment2.appointment_date and
|
||||
appointment1.doctor == appointment2.doctor and
|
||||
(
|
||||
(appointment1.appointment_time <= appointment2.appointment_time < appointment1.end_time) or
|
||||
(appointment2.appointment_time <= appointment1.appointment_time < appointment2.end_time)
|
||||
)
|
||||
)
|
||||
|
||||
def test_telemedicine_appointment(self):
|
||||
"""Test telemedicine appointment features"""
|
||||
data = self.appointment_data.copy()
|
||||
data['is_telemedicine'] = True
|
||||
data['telemedicine_link'] = 'https://meet.test.com/room/12345'
|
||||
|
||||
appointment = Appointment.objects.create(**data)
|
||||
self.assertTrue(appointment.is_telemedicine)
|
||||
self.assertEqual(appointment.telemedicine_link, data['telemedicine_link'])
|
||||
|
||||
def test_appointment_reminder_features(self):
|
||||
"""Test appointment reminder features"""
|
||||
appointment = Appointment.objects.create(**self.appointment_data)
|
||||
|
||||
# Initially no reminder sent
|
||||
self.assertFalse(appointment.reminder_sent)
|
||||
|
||||
# Mark reminder as sent
|
||||
appointment.reminder_sent = True
|
||||
appointment.reminder_sent_at = timezone.now()
|
||||
appointment.save()
|
||||
|
||||
self.assertTrue(appointment.reminder_sent)
|
||||
self.assertIsNotNone(appointment.reminder_sent_at)
|
||||
470
backend/tests/unit/models/test_logistics_models.py
Normal file
470
backend/tests/unit/models/test_logistics_models.py
Normal file
@@ -0,0 +1,470 @@
|
||||
"""
|
||||
Unit tests for Logistics Models
|
||||
|
||||
Tests for logistics module models:
|
||||
- Shipment
|
||||
- Vehicle
|
||||
|
||||
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.logistics.models.shipment import Shipment
|
||||
from backend.src.modules.logistics.models.vehicle import Vehicle
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class ShipmentModelTest(TestCase):
|
||||
"""Test cases for Shipment model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Logistics Sdn Bhd',
|
||||
schema_name='test_logistics',
|
||||
domain='testlogistics.com',
|
||||
business_type='logistics'
|
||||
)
|
||||
|
||||
self.user = User.objects.create_user(
|
||||
username='dispatcher',
|
||||
email='dispatcher@test.com',
|
||||
password='test123',
|
||||
tenant=self.tenant,
|
||||
role='staff'
|
||||
)
|
||||
|
||||
self.shipment_data = {
|
||||
'tenant': self.tenant,
|
||||
'tracking_number': 'TRK-2024010001-MY',
|
||||
'order_number': 'ORD-2024010001',
|
||||
'sender_name': 'Test Sender',
|
||||
'sender_company': 'Test Company',
|
||||
'sender_phone': '+60123456789',
|
||||
'sender_email': 'sender@test.com',
|
||||
'sender_address': '123 Sender Street',
|
||||
'sender_city': 'Kuala Lumpur',
|
||||
'sender_state': 'KUL',
|
||||
'sender_postal_code': '50000',
|
||||
'receiver_name': 'Test Receiver',
|
||||
'receiver_company': 'Test Receiver Company',
|
||||
'receiver_phone': '+60123456788',
|
||||
'receiver_email': 'receiver@test.com',
|
||||
'receiver_address': '456 Receiver Street',
|
||||
'receiver_city': 'Penang',
|
||||
'receiver_state': 'PNG',
|
||||
'receiver_postal_code': '10000',
|
||||
'origin_state': 'KUL',
|
||||
'destination_state': 'PNG',
|
||||
'service_type': 'express',
|
||||
'package_type': 'document',
|
||||
'weight': Decimal('1.5'),
|
||||
'length': Decimal('30.0'),
|
||||
'width': Decimal('20.0'),
|
||||
'height': Decimal('10.0'),
|
||||
'declared_value': Decimal('100.00'),
|
||||
'currency': 'MYR',
|
||||
'shipping_cost': Decimal('15.00'),
|
||||
'payment_method': 'cash',
|
||||
'payment_status': 'paid',
|
||||
'status': 'processing',
|
||||
'priority': 'normal',
|
||||
'special_instructions': 'Handle with care',
|
||||
'insurance_required': False,
|
||||
'insurance_amount': Decimal('0.00'),
|
||||
'estimated_delivery': date.today() + timedelta(days=2),
|
||||
'actual_delivery': None,
|
||||
'proof_of_delivery': '',
|
||||
'delivery_confirmation': False,
|
||||
'created_by': self.user
|
||||
}
|
||||
|
||||
def test_create_shipment(self):
|
||||
"""Test creating a new shipment"""
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
self.assertEqual(shipment.tenant, self.tenant)
|
||||
self.assertEqual(shipment.tracking_number, self.shipment_data['tracking_number'])
|
||||
self.assertEqual(shipment.order_number, self.shipment_data['order_number'])
|
||||
self.assertEqual(shipment.sender_name, self.shipment_data['sender_name'])
|
||||
self.assertEqual(shipment.receiver_name, self.shipment_data['receiver_name'])
|
||||
self.assertEqual(shipment.service_type, self.shipment_data['service_type'])
|
||||
self.assertEqual(shipment.weight, self.shipment_data['weight'])
|
||||
self.assertEqual(shipment.shipping_cost, self.shipment_data['shipping_cost'])
|
||||
self.assertEqual(shipment.status, self.shipment_data['status'])
|
||||
|
||||
def test_shipment_string_representation(self):
|
||||
"""Test shipment string representation"""
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
self.assertEqual(str(shipment), f"{shipment.tracking_number} - {shipment.sender_name} to {shipment.receiver_name}")
|
||||
|
||||
def test_shipment_volume_calculation(self):
|
||||
"""Test shipment volume calculation"""
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
|
||||
# Volume = length × width × height (in cm)
|
||||
expected_volume = Decimal('6000.0') # 30.0 × 20.0 × 10.0
|
||||
self.assertEqual(shipment.volume, expected_volume)
|
||||
|
||||
def test_shipment_delivery_status(self):
|
||||
"""Test shipment delivery status"""
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
|
||||
# Not delivered yet
|
||||
self.assertFalse(shipment.is_delivered)
|
||||
|
||||
# Mark as delivered
|
||||
shipment.status = 'delivered'
|
||||
shipment.actual_delivery = date.today()
|
||||
shipment.delivery_confirmation = True
|
||||
shipment.save()
|
||||
|
||||
self.assertTrue(shipment.is_delivered)
|
||||
|
||||
def test_shipment_delayed_status(self):
|
||||
"""Test shipment delayed status"""
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
|
||||
# Not delayed (estimated delivery is in future)
|
||||
self.assertFalse(shipment.is_delayed)
|
||||
|
||||
# Mark as delayed (past estimated delivery)
|
||||
shipment.estimated_delivery = date.today() - timedelta(days=1)
|
||||
shipment.status = 'in_transit'
|
||||
shipment.save()
|
||||
|
||||
self.assertTrue(shipment.is_delayed)
|
||||
|
||||
def test_shipment_service_type_choices(self):
|
||||
"""Test shipment service type validation"""
|
||||
invalid_data = self.shipment_data.copy()
|
||||
invalid_data['service_type'] = 'invalid_service'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**invalid_data)
|
||||
|
||||
def test_shipment_package_type_choices(self):
|
||||
"""Test shipment package type validation"""
|
||||
invalid_data = self.shipment_data.copy()
|
||||
invalid_data['package_type'] = 'invalid_package'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**invalid_data)
|
||||
|
||||
def test_shipment_status_choices(self):
|
||||
"""Test shipment status validation"""
|
||||
invalid_data = self.shipment_data.copy()
|
||||
invalid_data['status'] = 'invalid_status'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**invalid_data)
|
||||
|
||||
def test_shipment_priority_choices(self):
|
||||
"""Test shipment priority validation"""
|
||||
invalid_data = self.shipment_data.copy()
|
||||
invalid_data['priority'] = 'invalid_priority'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**invalid_data)
|
||||
|
||||
def test_shipment_malaysian_phone_validation(self):
|
||||
"""Test Malaysian phone number validation"""
|
||||
# Valid Malaysian phone numbers
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
self.assertEqual(shipment.sender_phone, self.shipment_data['sender_phone'])
|
||||
self.assertEqual(shipment.receiver_phone, self.shipment_data['receiver_phone'])
|
||||
|
||||
# Invalid sender phone
|
||||
invalid_data = self.shipment_data.copy()
|
||||
invalid_data['sender_phone'] = '12345'
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**invalid_data)
|
||||
|
||||
# Invalid receiver phone
|
||||
invalid_data = self.shipment_data.copy()
|
||||
invalid_data['receiver_phone'] = '67890'
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**invalid_data)
|
||||
|
||||
def test_shipment_malaysian_state_validation(self):
|
||||
"""Test Malaysian state validation"""
|
||||
# Valid Malaysian states
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
self.assertEqual(shipment.sender_state, self.shipment_data['sender_state'])
|
||||
self.assertEqual(shipment.receiver_state, self.shipment_data['receiver_state'])
|
||||
|
||||
# Invalid sender state
|
||||
invalid_data = self.shipment_data.copy()
|
||||
invalid_data['sender_state'] = 'XX'
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**invalid_data)
|
||||
|
||||
def test_shipment_weight_validation(self):
|
||||
"""Test shipment weight validation"""
|
||||
# Valid weight
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
self.assertEqual(shipment.weight, self.shipment_data['weight'])
|
||||
|
||||
# Invalid weight (negative)
|
||||
invalid_data = self.shipment_data.copy()
|
||||
invalid_data['weight'] = Decimal('-1.0')
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**invalid_data)
|
||||
|
||||
def test_shipment_tracking_number_format(self):
|
||||
"""Test shipment tracking number format"""
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
|
||||
# Should end with -MY for Malaysia
|
||||
self.assertTrue(shipment.tracking_number.endswith('-MY'))
|
||||
|
||||
# Should be unique
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**self.shipment_data)
|
||||
|
||||
def test_shipment_dimensions_validation(self):
|
||||
"""Test shipment dimensions validation"""
|
||||
# Valid dimensions
|
||||
shipment = Shipment.objects.create(**self.shipment_data)
|
||||
self.assertEqual(shipment.length, self.shipment_data['length'])
|
||||
self.assertEqual(shipment.width, self.shipment_data['width'])
|
||||
self.assertEqual(shipment.height, self.shipment_data['height'])
|
||||
|
||||
# Invalid dimensions (negative)
|
||||
invalid_data = self.shipment_data.copy()
|
||||
invalid_data['length'] = Decimal('-1.0')
|
||||
with self.assertRaises(Exception):
|
||||
Shipment.objects.create(**invalid_data)
|
||||
|
||||
|
||||
class VehicleModelTest(TestCase):
|
||||
"""Test cases for Vehicle model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Logistics Sdn Bhd',
|
||||
schema_name='test_logistics',
|
||||
domain='testlogistics.com',
|
||||
business_type='logistics'
|
||||
)
|
||||
|
||||
self.user = User.objects.create_user(
|
||||
username='manager',
|
||||
email='manager@test.com',
|
||||
password='test123',
|
||||
tenant=self.tenant,
|
||||
role='admin'
|
||||
)
|
||||
|
||||
self.vehicle_data = {
|
||||
'tenant': self.tenant,
|
||||
'vehicle_number': 'V1234',
|
||||
'registration_number': 'WAB1234',
|
||||
'vehicle_type': 'van',
|
||||
'make': 'Toyota',
|
||||
'model': 'Hiace',
|
||||
'year': 2020,
|
||||
'color': 'White',
|
||||
'chassis_number': 'MR0HE3CD5L123456',
|
||||
'engine_number': '2TR123456',
|
||||
'capacity': 1000, # kg
|
||||
'volume_capacity': 10.0, # cubic meters
|
||||
'fuel_type': 'petrol',
|
||||
'fuel_capacity': 70, # liters
|
||||
'current_fuel': 50, # liters
|
||||
'purchase_date': date(2020, 1, 1),
|
||||
'purchase_price': Decimal('120000.00'),
|
||||
'insurance_policy': 'INS-2024-001234',
|
||||
'insurance_expiry': date.today() + timedelta(days=365),
|
||||
'road_tax_expiry': date.today() + timedelta(days=180),
|
||||
'inspection_expiry': date.today() + timedelta(days=90),
|
||||
'current_mileage': 50000,
|
||||
'last_service_mileage': 45000,
|
||||
'next_service_mileage': 55000,
|
||||
'status': 'active',
|
||||
'assigned_driver': None,
|
||||
'gps_device_id': 'GPS001234',
|
||||
'is_active': True,
|
||||
'notes': 'Well-maintained vehicle',
|
||||
'created_by': self.user
|
||||
}
|
||||
|
||||
def test_create_vehicle(self):
|
||||
"""Test creating a new vehicle"""
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
self.assertEqual(vehicle.tenant, self.tenant)
|
||||
self.assertEqual(vehicle.vehicle_number, self.vehicle_data['vehicle_number'])
|
||||
self.assertEqual(vehicle.registration_number, self.vehicle_data['registration_number'])
|
||||
self.assertEqual(vehicle.vehicle_type, self.vehicle_data['vehicle_type'])
|
||||
self.assertEqual(vehicle.make, self.vehicle_data['make'])
|
||||
self.assertEqual(vehicle.model, self.vehicle_data['model'])
|
||||
self.assertEqual(vehicle.year, self.vehicle_data['year'])
|
||||
self.assertEqual(vehicle.capacity, self.vehicle_data['capacity'])
|
||||
self.assertEqual(vehicle.status, self.vehicle_data['status'])
|
||||
self.assertTrue(vehicle.is_active)
|
||||
|
||||
def test_vehicle_string_representation(self):
|
||||
"""Test vehicle string representation"""
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
self.assertEqual(str(vehicle), f"{vehicle.make} {vehicle.model} ({vehicle.registration_number})")
|
||||
|
||||
def test_vehicle_age(self):
|
||||
"""Test vehicle age calculation"""
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
|
||||
# Age should be calculated based on purchase date
|
||||
today = date.today()
|
||||
expected_age = today.year - vehicle.purchase_date.year
|
||||
if today.month < vehicle.purchase_date.month or (today.month == vehicle.purchase_date.month and today.day < vehicle.purchase_date.day):
|
||||
expected_age -= 1
|
||||
|
||||
self.assertEqual(vehicle.age, expected_age)
|
||||
|
||||
def test_vehicle_service_due(self):
|
||||
"""Test vehicle service due status"""
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
|
||||
# Service not due yet
|
||||
self.assertFalse(vehicle.service_due)
|
||||
|
||||
# Mark as service due
|
||||
vehicle.current_mileage = 56000
|
||||
vehicle.save()
|
||||
|
||||
self.assertTrue(vehicle.service_due)
|
||||
|
||||
def test_vehicle_insurance_expiry_status(self):
|
||||
"""Test vehicle insurance expiry status"""
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
|
||||
# Insurance not expired
|
||||
self.assertFalse(vehicle.insurance_expired)
|
||||
|
||||
# Mark as expired
|
||||
vehicle.insurance_expiry = date.today() - timedelta(days=1)
|
||||
vehicle.save()
|
||||
|
||||
self.assertTrue(vehicle.insurance_expired)
|
||||
|
||||
def test_vehicle_road_tax_expiry_status(self):
|
||||
"""Test vehicle road tax expiry status"""
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
|
||||
# Road tax not expired
|
||||
self.assertFalse(vehicle.road_tax_expired)
|
||||
|
||||
# Mark as expired
|
||||
vehicle.road_tax_expiry = date.today() - timedelta(days=1)
|
||||
vehicle.save()
|
||||
|
||||
self.assertTrue(vehicle.road_tax_expired)
|
||||
|
||||
def test_vehicle_inspection_expiry_status(self):
|
||||
"""Test vehicle inspection expiry status"""
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
|
||||
# Inspection not expired
|
||||
self.assertFalse(vehicle.inspection_expired)
|
||||
|
||||
# Mark as expired
|
||||
vehicle.inspection_expiry = date.today() - timedelta(days=1)
|
||||
vehicle.save()
|
||||
|
||||
self.assertTrue(vehicle.inspection_expired)
|
||||
|
||||
def test_vehicle_type_choices(self):
|
||||
"""Test vehicle type validation"""
|
||||
invalid_data = self.vehicle_data.copy()
|
||||
invalid_data['vehicle_type'] = 'invalid_type'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Vehicle.objects.create(**invalid_data)
|
||||
|
||||
def test_vehicle_fuel_type_choices(self):
|
||||
"""Test vehicle fuel type validation"""
|
||||
invalid_data = self.vehicle_data.copy()
|
||||
invalid_data['fuel_type'] = 'invalid_fuel'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Vehicle.objects.create(**invalid_data)
|
||||
|
||||
def test_vehicle_status_choices(self):
|
||||
"""Test vehicle status validation"""
|
||||
invalid_data = self.vehicle_data.copy()
|
||||
invalid_data['status'] = 'invalid_status'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Vehicle.objects.create(**invalid_data)
|
||||
|
||||
def test_vehicle_malaysian_registration_validation(self):
|
||||
"""Test Malaysian vehicle registration validation"""
|
||||
# Valid registration number
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
self.assertEqual(vehicle.registration_number, self.vehicle_data['registration_number'])
|
||||
|
||||
# Invalid registration number format
|
||||
invalid_data = self.vehicle_data.copy()
|
||||
invalid_data['registration_number'] = 'ABC123'
|
||||
with self.assertRaises(Exception):
|
||||
Vehicle.objects.create(**invalid_data)
|
||||
|
||||
def test_vehicle_mileage_validation(self):
|
||||
"""Test vehicle mileage validation"""
|
||||
# Valid mileage
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
self.assertEqual(vehicle.current_mileage, self.vehicle_data['current_mileage'])
|
||||
|
||||
# Invalid mileage (negative)
|
||||
invalid_data = self.vehicle_data.copy()
|
||||
invalid_data['current_mileage'] = -1000
|
||||
with self.assertRaises(Exception):
|
||||
Vehicle.objects.create(**invalid_data)
|
||||
|
||||
def test_vehicle_capacity_validation(self):
|
||||
"""Test vehicle capacity validation"""
|
||||
# Valid capacity
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
self.assertEqual(vehicle.capacity, self.vehicle_data['capacity'])
|
||||
|
||||
# Invalid capacity (negative)
|
||||
invalid_data = self.vehicle_data.copy()
|
||||
invalid_data['capacity'] = -100
|
||||
with self.assertRaises(Exception):
|
||||
Vehicle.objects.create(**invalid_data)
|
||||
|
||||
def test_vehicle_year_validation(self):
|
||||
"""Test vehicle year validation"""
|
||||
# Valid year
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
self.assertEqual(vehicle.year, self.vehicle_data['year'])
|
||||
|
||||
# Invalid year (too old)
|
||||
invalid_data = self.vehicle_data.copy()
|
||||
invalid_data['year'] = 1950
|
||||
with self.assertRaises(Exception):
|
||||
Vehicle.objects.create(**invalid_data)
|
||||
|
||||
def test_vehicle_fuel_level_validation(self):
|
||||
"""Test vehicle fuel level validation"""
|
||||
# Valid fuel level
|
||||
vehicle = Vehicle.objects.create(**self.vehicle_data)
|
||||
self.assertEqual(vehicle.current_fuel, self.vehicle_data['current_fuel'])
|
||||
|
||||
# Invalid fuel level (negative)
|
||||
invalid_data = self.vehicle_data.copy()
|
||||
invalid_data['current_fuel'] = -10
|
||||
with self.assertRaises(Exception):
|
||||
Vehicle.objects.create(**invalid_data)
|
||||
|
||||
# Invalid fuel level (exceeds capacity)
|
||||
invalid_data = self.vehicle_data.copy()
|
||||
invalid_data['current_fuel'] = 100
|
||||
with self.assertRaises(Exception):
|
||||
Vehicle.objects.create(**invalid_data)
|
||||
350
backend/tests/unit/models/test_retail_models.py
Normal file
350
backend/tests/unit/models/test_retail_models.py
Normal file
@@ -0,0 +1,350 @@
|
||||
"""
|
||||
Unit tests for Retail Models
|
||||
|
||||
Tests for retail module models:
|
||||
- Product
|
||||
- Sale
|
||||
|
||||
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
|
||||
|
||||
from backend.src.core.models.tenant import Tenant
|
||||
from backend.src.core.models.user import User
|
||||
from backend.src.modules.retail.models.product import Product
|
||||
from backend.src.modules.retail.models.sale import Sale, SaleItem
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class ProductModelTest(TestCase):
|
||||
"""Test cases for Product model"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Business Sdn Bhd',
|
||||
schema_name='test_business',
|
||||
domain='testbusiness.com',
|
||||
business_type='retail'
|
||||
)
|
||||
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='user@test.com',
|
||||
password='test123',
|
||||
tenant=self.tenant
|
||||
)
|
||||
|
||||
self.product_data = {
|
||||
'tenant': self.tenant,
|
||||
'sku': 'PRD-001',
|
||||
'name': 'Test Product',
|
||||
'description': 'A test product for unit testing',
|
||||
'category': 'electronics',
|
||||
'brand': 'Test Brand',
|
||||
'barcode': '1234567890123',
|
||||
'unit': 'piece',
|
||||
'current_stock': 100,
|
||||
'minimum_stock': 10,
|
||||
'maximum_stock': 500,
|
||||
'reorder_point': 15,
|
||||
'purchase_price': Decimal('50.00'),
|
||||
'selling_price': Decimal('100.00'),
|
||||
'wholesale_price': Decimal('80.00'),
|
||||
'tax_rate': 10.0,
|
||||
'is_taxable': True,
|
||||
'is_active': True,
|
||||
'requires_prescription': False,
|
||||
'is_halal': True,
|
||||
'msme_certified': True,
|
||||
'created_by': self.user
|
||||
}
|
||||
|
||||
def test_create_product(self):
|
||||
"""Test creating a new product"""
|
||||
product = Product.objects.create(**self.product_data)
|
||||
self.assertEqual(product.tenant, self.tenant)
|
||||
self.assertEqual(product.sku, self.product_data['sku'])
|
||||
self.assertEqual(product.name, self.product_data['name'])
|
||||
self.assertEqual(product.current_stock, self.product_data['current_stock'])
|
||||
self.assertEqual(product.purchase_price, self.product_data['purchase_price'])
|
||||
self.assertEqual(product.selling_price, self.product_data['selling_price'])
|
||||
self.assertTrue(product.is_active)
|
||||
self.assertTrue(product.is_halal)
|
||||
|
||||
def test_product_string_representation(self):
|
||||
"""Test product string representation"""
|
||||
product = Product.objects.create(**self.product_data)
|
||||
self.assertEqual(str(product), f"{product.name} ({product.sku})")
|
||||
|
||||
def test_product_is_low_stock(self):
|
||||
"""Test product low stock detection"""
|
||||
product = Product.objects.create(**self.product_data)
|
||||
|
||||
# Normal stock level
|
||||
self.assertFalse(product.is_low_stock)
|
||||
|
||||
# Low stock level
|
||||
product.current_stock = 5
|
||||
product.save()
|
||||
self.assertTrue(product.is_low_stock)
|
||||
|
||||
def test_product_profit_margin(self):
|
||||
"""Test product profit margin calculation"""
|
||||
product = Product.objects.create(**self.product_data)
|
||||
|
||||
expected_margin = ((product.selling_price - product.purchase_price) / product.selling_price) * 100
|
||||
self.assertAlmostEqual(product.profit_margin, expected_margin)
|
||||
|
||||
def test_product_category_choices(self):
|
||||
"""Test product category validation"""
|
||||
invalid_data = self.product_data.copy()
|
||||
invalid_data['category'] = 'invalid_category'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Product.objects.create(**invalid_data)
|
||||
|
||||
def test_product_unit_choices(self):
|
||||
"""Test product unit validation"""
|
||||
invalid_data = self.product_data.copy()
|
||||
invalid_data['unit'] = 'invalid_unit'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Product.objects.create(**invalid_data)
|
||||
|
||||
def test_product_barcode_validation(self):
|
||||
"""Test product barcode validation"""
|
||||
# Valid barcode
|
||||
product = Product.objects.create(**self.product_data)
|
||||
self.assertEqual(product.barcode, self.product_data['barcode'])
|
||||
|
||||
# Invalid barcode (too long)
|
||||
invalid_data = self.product_data.copy()
|
||||
invalid_data['barcode'] = '1' * 14
|
||||
with self.assertRaises(Exception):
|
||||
Product.objects.create(**invalid_data)
|
||||
|
||||
def test_product_stock_validation(self):
|
||||
"""Test product stock validation"""
|
||||
invalid_data = self.product_data.copy()
|
||||
invalid_data['current_stock'] = -1
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Product.objects.create(**invalid_data)
|
||||
|
||||
invalid_data['current_stock'] = 0
|
||||
invalid_data['minimum_stock'] = -5
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Product.objects.create(**invalid_data)
|
||||
|
||||
|
||||
class SaleModelTest(TestCase):
|
||||
"""Test cases for Sale and SaleItem models"""
|
||||
|
||||
def setUp(self):
|
||||
self.tenant = Tenant.objects.create(
|
||||
name='Test Business Sdn Bhd',
|
||||
schema_name='test_business',
|
||||
domain='testbusiness.com',
|
||||
business_type='retail'
|
||||
)
|
||||
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='user@test.com',
|
||||
password='test123',
|
||||
tenant=self.tenant
|
||||
)
|
||||
|
||||
self.product1 = Product.objects.create(
|
||||
tenant=self.tenant,
|
||||
sku='PRD-001',
|
||||
name='Product 1',
|
||||
category='electronics',
|
||||
unit='piece',
|
||||
current_stock=100,
|
||||
minimum_stock=10,
|
||||
purchase_price=Decimal('50.00'),
|
||||
selling_price=Decimal('100.00'),
|
||||
tax_rate=10.0,
|
||||
created_by=self.user
|
||||
)
|
||||
|
||||
self.product2 = Product.objects.create(
|
||||
tenant=self.tenant,
|
||||
sku='PRD-002',
|
||||
name='Product 2',
|
||||
category='electronics',
|
||||
unit='piece',
|
||||
current_stock=50,
|
||||
minimum_stock=5,
|
||||
purchase_price=Decimal('30.00'),
|
||||
selling_price=Decimal('60.00'),
|
||||
tax_rate=10.0,
|
||||
created_by=self.user
|
||||
)
|
||||
|
||||
self.sale_data = {
|
||||
'tenant': self.tenant,
|
||||
'invoice_number': 'INV-2024010001',
|
||||
'customer_name': 'Test Customer',
|
||||
'customer_email': 'customer@test.com',
|
||||
'customer_phone': '+60123456789',
|
||||
'customer_ic': '000101-01-0001',
|
||||
'sale_date': timezone.now(),
|
||||
'status': 'completed',
|
||||
'payment_method': 'cash',
|
||||
'payment_status': 'paid',
|
||||
'sales_person': self.user,
|
||||
'notes': 'Test sale for unit testing'
|
||||
}
|
||||
|
||||
def test_create_sale(self):
|
||||
"""Test creating a new sale"""
|
||||
sale = Sale.objects.create(**self.sale_data)
|
||||
self.assertEqual(sale.tenant, self.tenant)
|
||||
self.assertEqual(sale.invoice_number, self.sale_data['invoice_number'])
|
||||
self.assertEqual(sale.customer_name, self.sale_data['customer_name'])
|
||||
self.assertEqual(sale.status, self.sale_data['status'])
|
||||
self.assertEqual(sale.payment_status, self.sale_data['payment_status'])
|
||||
self.assertEqual(sale.sales_person, self.user)
|
||||
|
||||
def test_sale_string_representation(self):
|
||||
"""Test sale string representation"""
|
||||
sale = Sale.objects.create(**self.sale_data)
|
||||
self.assertEqual(str(sale), f"Invoice #{sale.invoice_number} - {sale.customer_name}")
|
||||
|
||||
def test_create_sale_item(self):
|
||||
"""Test creating a sale item"""
|
||||
sale = Sale.objects.create(**self.sale_data)
|
||||
|
||||
sale_item_data = {
|
||||
'sale': sale,
|
||||
'product': self.product1,
|
||||
'quantity': 2,
|
||||
'unit_price': Decimal('100.00'),
|
||||
'discount_percentage': 0.0,
|
||||
'tax_rate': 10.0,
|
||||
'notes': 'Test sale item'
|
||||
}
|
||||
|
||||
sale_item = SaleItem.objects.create(**sale_item_data)
|
||||
self.assertEqual(sale_item.sale, sale)
|
||||
self.assertEqual(sale_item.product, self.product1)
|
||||
self.assertEqual(sale_item.quantity, 2)
|
||||
self.assertEqual(sale_item.unit_price, Decimal('100.00'))
|
||||
|
||||
def test_sale_item_subtotal(self):
|
||||
"""Test sale item subtotal calculation"""
|
||||
sale = Sale.objects.create(**self.sale_data)
|
||||
|
||||
sale_item = SaleItem.objects.create(
|
||||
sale=sale,
|
||||
product=self.product1,
|
||||
quantity=2,
|
||||
unit_price=Decimal('100.00'),
|
||||
tax_rate=10.0
|
||||
)
|
||||
|
||||
expected_subtotal = Decimal('200.00') # 2 * 100.00
|
||||
self.assertEqual(sale_item.subtotal, expected_subtotal)
|
||||
|
||||
def test_sale_item_tax_amount(self):
|
||||
"""Test sale item tax amount calculation"""
|
||||
sale = Sale.objects.create(**self.sale_data)
|
||||
|
||||
sale_item = SaleItem.objects.create(
|
||||
sale=sale,
|
||||
product=self.product1,
|
||||
quantity=2,
|
||||
unit_price=Decimal('100.00'),
|
||||
tax_rate=10.0
|
||||
)
|
||||
|
||||
expected_tax = Decimal('20.00') # 200.00 * 0.10
|
||||
self.assertEqual(sale_item.tax_amount, expected_tax)
|
||||
|
||||
def test_sale_item_total_amount(self):
|
||||
"""Test sale item total amount calculation"""
|
||||
sale = Sale.objects.create(**self.sale_data)
|
||||
|
||||
sale_item = SaleItem.objects.create(
|
||||
sale=sale,
|
||||
product=self.product1,
|
||||
quantity=2,
|
||||
unit_price=Decimal('100.00'),
|
||||
tax_rate=10.0
|
||||
)
|
||||
|
||||
expected_total = Decimal('220.00') # 200.00 + 20.00
|
||||
self.assertEqual(sale_item.total_amount, expected_total)
|
||||
|
||||
def test_sale_calculate_totals(self):
|
||||
"""Test sale total calculations"""
|
||||
sale = Sale.objects.create(**self.sale_data)
|
||||
|
||||
# Create multiple sale items
|
||||
SaleItem.objects.create(
|
||||
sale=sale,
|
||||
product=self.product1,
|
||||
quantity=2,
|
||||
unit_price=Decimal('100.00'),
|
||||
tax_rate=10.0
|
||||
)
|
||||
|
||||
SaleItem.objects.create(
|
||||
sale=sale,
|
||||
product=self.product2,
|
||||
quantity=1,
|
||||
unit_price=Decimal('60.00'),
|
||||
tax_rate=10.0
|
||||
)
|
||||
|
||||
# Test the calculate_totals method
|
||||
sale.calculate_totals()
|
||||
|
||||
expected_subtotal = Decimal('260.00') # 200.00 + 60.00
|
||||
expected_tax = Decimal('26.00') # 20.00 + 6.00
|
||||
expected_total = Decimal('286.00') # 260.00 + 26.00
|
||||
|
||||
self.assertEqual(sale.subtotal_amount, expected_subtotal)
|
||||
self.assertEqual(sale.tax_amount, expected_tax)
|
||||
self.assertEqual(sale.total_amount, expected_total)
|
||||
|
||||
def test_sale_status_choices(self):
|
||||
"""Test sale status validation"""
|
||||
invalid_data = self.sale_data.copy()
|
||||
invalid_data['status'] = 'invalid_status'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Sale.objects.create(**invalid_data)
|
||||
|
||||
def test_sale_payment_method_choices(self):
|
||||
"""Test sale payment method validation"""
|
||||
invalid_data = self.sale_data.copy()
|
||||
invalid_data['payment_method'] = 'invalid_method'
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
Sale.objects.create(**invalid_data)
|
||||
|
||||
def test_malaysian_customer_validation(self):
|
||||
"""Test Malaysian customer validation"""
|
||||
# Valid Malaysian IC
|
||||
sale = Sale.objects.create(**self.sale_data)
|
||||
self.assertEqual(sale.customer_ic, self.sale_data['customer_ic'])
|
||||
|
||||
# Valid Malaysian phone
|
||||
self.assertEqual(sale.customer_phone, self.sale_data['customer_phone'])
|
||||
|
||||
# Invalid phone number
|
||||
invalid_data = self.sale_data.copy()
|
||||
invalid_data['customer_phone'] = '12345'
|
||||
with self.assertRaises(Exception):
|
||||
Sale.objects.create(**invalid_data)
|
||||
Reference in New Issue
Block a user