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:
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)
|
||||
Reference in New Issue
Block a user