""" 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)