import 'package:flutter_test/flutter_test.dart'; import 'package:metatron_student_erp/core/utils/safe_convert.dart'; import 'package:metatron_student_erp/models/attendance.dart'; import 'package:metatron_student_erp/models/announcement.dart'; import 'package:metatron_student_erp/models/dashboard.dart'; import 'package:metatron_student_erp/models/exam.dart'; import 'package:metatron_student_erp/models/fee.dart'; import 'package:metatron_student_erp/models/result.dart'; import 'package:metatron_student_erp/models/student.dart'; import 'package:metatron_student_erp/models/timetable.dart'; import 'package:metatron_student_erp/models/user.dart'; void main() { group('SafeConvert Utility Tests', () { test('handles Odoo false, null, and empty values gracefully', () { expect(SafeConvert.toNullableString(false), isNull); expect(SafeConvert.toNullableString(null), isNull); expect(SafeConvert.toNullableString('hello'), 'hello'); expect(SafeConvert.toSafeString(false, 'default'), 'default'); expect(SafeConvert.toNullableInt(false), isNull); expect(SafeConvert.toNullableInt(null), isNull); expect(SafeConvert.toNullableInt(42), 42); expect(SafeConvert.toNullableInt('42'), 42); expect(SafeConvert.toSafeInt(false, 0), 0); expect(SafeConvert.toNullableDouble(false), isNull); expect(SafeConvert.toNullableDouble(null), isNull); expect(SafeConvert.toNullableDouble(85.5), 85.5); expect(SafeConvert.toNullableDouble('85.5'), 85.5); expect(SafeConvert.toSafeDouble(false, 0.0), 0.0); expect(SafeConvert.toSafeBool(false), isFalse); expect(SafeConvert.toSafeBool(true), isTrue); expect(SafeConvert.toSafeBool(null), isFalse); expect(SafeConvert.toSafeBool('true'), isTrue); expect(SafeConvert.toNullableMap(false), isNull); expect(SafeConvert.toNullableMap({'a': 1}), {'a': 1}); expect(SafeConvert.toSafeList(false, (x) => x), isEmpty); expect(SafeConvert.toSafeList([1, 2], (x) => x * 2), [2, 4]); }); }); group('Student Profile Parsing with Odoo false fields', () { test('parses full student profile with mixed false and real values', () { final json = { 'id': 4, 'student_id': 'STU-9999', 'admission_number': 'ADM-9999', 'roll_number': false, // Odoo boolean false 'full_name': 'Demo Student', 'email': false, // Odoo boolean false 'mobile': '9876543210', 'gender': 'male', 'date_of_birth': '2000-01-01', 'status': 'active', 'campus': {'id': 11, 'name': 'Main Campus'}, 'department': false, // Odoo boolean false 'program': {'id': 11, 'name': 'B.Tech CS'}, 'level': false, 'academic_year': {'id': 10, 'name': '2026-2027'}, 'academic_term': {'id': 9, 'name': 'Semester 1'}, 'batch': {'id': 3, 'name': 'Batch 2026'}, 'semester': {'id': 3, 'name': 'Sem 1'}, 'section': {'id': 2, 'name': 'Section A'}, }; final profile = StudentProfile.fromJson(json); expect(profile.id, 4); expect(profile.studentId, 'STU-9999'); expect(profile.admissionNumber, 'ADM-9999'); expect(profile.rollNumber, isNull); // Was false in JSON expect(profile.email, isNull); // Was false in JSON expect(profile.mobile, '9876543210'); expect(profile.fullName, 'Demo Student'); expect(profile.campus?.name, 'Main Campus'); expect(profile.department, isNull); // Was false in JSON expect(profile.program?.name, 'B.Tech CS'); expect(profile.academicYear?.name, '2026-2027'); }); }); group('Attendance Parsing Tests', () { test('parses attendance summary and record lines', () { final json = { 'summary': { 'percentage': 87.5, 'present': 14, 'absent': 2, 'late': 1, 'medical_leave': 0, 'on_duty': 0, }, 'records': [ {'id': 101, 'date': '2026-10-12', 'status': 'present', 'remarks': 'On time'}, {'id': 102, 'date': '2026-10-11', 'status': 'absent'}, {'id': 103, 'date': '2026-10-10', 'status': 'late', 'remarks': 'Bus delay 15 mins'}, ], }; final data = AttendanceData.fromJson(json); expect(data.summary.percentage, 87.5); expect(data.summary.present, 14); expect(data.summary.absent, 2); expect(data.records.length, 3); expect(data.records[0].date, '2026-10-12'); expect(data.records[0].status, 'present'); expect(data.records[0].remarks, 'On time'); expect(data.records[1].status, 'absent'); expect(data.records[1].remarks, ''); expect(data.records[2].status, 'late'); expect(data.records[2].remarks, 'Bus delay 15 mins'); }); test('handles empty or false attendance gracefully', () { final data = AttendanceData.fromJson({ 'summary': false, 'records': false, }); expect(data.summary.percentage, 0.0); expect(data.summary.present, 0); expect(data.records, isEmpty); }); test('parses all attendance status types correctly', () { final records = [ {'id': 1, 'date': '2026-10-01', 'status': 'present'}, {'id': 2, 'date': '2026-10-02', 'status': 'absent'}, {'id': 3, 'date': '2026-10-03', 'status': 'late'}, {'id': 4, 'date': '2026-10-04', 'status': 'medical_leave'}, {'id': 5, 'date': '2026-10-05', 'status': 'on_duty'}, ]; final data = AttendanceData.fromJson({ 'summary': { 'percentage': 80.0, 'present': 1, 'absent': 1, 'late': 1, 'medical_leave': 1, 'on_duty': 1, }, 'records': records, }); expect(data.records.length, 5); expect(data.summary.medicalLeave, 1); expect(data.summary.onDuty, 1); expect(data.records[3].status, 'medical_leave'); expect(data.records[4].status, 'on_duty'); }); }); group('Timetable Parsing Tests', () { test('parses timetable slots with faculty and room', () { final json = { 'id': 1, 'date': '2026-10-13', 'day': 'Tuesday', 'start_time': '09:00 AM', 'end_time': '10:00 AM', 'subject': {'id': 1, 'name': 'Algorithms'}, 'faculty': {'id': 2, 'name': 'Dr. Raman'}, 'room': {'id': 3, 'name': 'Hall A'}, }; final slot = TimetableSlot.fromJson(json); expect(slot.id, 1); expect(slot.day, 'Tuesday'); expect(slot.startTime, '09:00 AM'); expect(slot.subject?.name, 'Algorithms'); expect(slot.faculty?.name, 'Dr. Raman'); expect(slot.room?.name, 'Hall A'); }); test('parses timetable slot with null and false optional fields safely', () { final json = { 'id': 2, 'date': '2026-10-14', 'day': 'Wednesday', 'start_time': '11:00 AM', 'end_time': '12:00 PM', 'subject': null, 'faculty': false, 'room': null, }; final slot = TimetableSlot.fromJson(json); expect(slot.id, 2); expect(slot.date, '2026-10-14'); expect(slot.day, 'Wednesday'); expect(slot.startTime, '11:00 AM'); expect(slot.endTime, '12:00 PM'); expect(slot.subject, isNull); expect(slot.faculty, isNull); expect(slot.room, isNull); }); test('handles empty or malformed timetable payload gracefully', () { final slot = TimetableSlot.fromJson({}); expect(slot.id, 0); expect(slot.date, ''); expect(slot.day, ''); expect(slot.startTime, ''); expect(slot.endTime, ''); expect(slot.subject, isNull); expect(slot.faculty, isNull); expect(slot.room, isNull); }); }); group('Exam and Result Parsing Tests', () { test('parses exam details', () { final json = { 'id': 5, 'name': 'Semester End Exam', 'state': 'scheduled', 'start_date': '2026-12-01', 'end_date': '2026-12-15', 'program': {'id': 1, 'name': 'B.Tech CS'}, }; final exam = Exam.fromJson(json); expect(exam.id, 5); expect(exam.name, 'Semester End Exam'); expect(exam.state, 'scheduled'); expect(exam.startDate, '2026-12-01'); expect(exam.program?.name, 'B.Tech CS'); }); test('parses exam results with breakdown lines', () { final json = { 'id': 10, 'exam': {'id': 5, 'name': 'Midterm Exam'}, 'total_max_marks': 200, 'total_obtained_marks': 175, 'percentage': 87.5, 'grade': 'A', 'status': 'pass', 'published_date': '2026-10-01', 'lines': [ { 'subject': {'id': 1, 'name': 'Mathematics'}, 'max_marks': 100, 'pass_marks': 40, 'obtained_marks': 90, 'percentage': 90.0, 'grade': 'A+', 'status': 'pass', }, { 'subject': {'id': 2, 'name': 'Physics'}, 'max_marks': 100, 'pass_marks': 40, 'obtained_marks': 85, 'percentage': 85.0, 'grade': 'A', 'status': 'pass', } ], }; final result = ExamResult.fromJson(json); expect(result.id, 10); expect(result.exam?.name, 'Midterm Exam'); expect(result.totalMaxMarks, 200.0); expect(result.totalObtainedMarks, 175.0); expect(result.percentage, 87.5); expect(result.grade, 'A'); expect(result.status, 'pass'); expect(result.lines.length, 2); expect(result.lines[0].subject?.name, 'Mathematics'); expect(result.lines[0].obtainedMarks, 90.0); expect(result.lines[1].subject?.name, 'Physics'); }); }); group('Dashboard Parsing Tests', () { test('parses complete dashboard payload', () { final json = { 'student': { 'id': 4, 'full_name': 'Demo Student', 'program': {'id': 11, 'name': 'B.Tech CS'}, }, 'attendance': { 'percentage': 92.0, 'present': 23, 'absent': 2, 'late': 0, 'medical_leave': 0, 'on_duty': 0, }, 'today_timetable': [ { 'id': 1, 'date': '2026-10-13', 'day': 'Tuesday', 'start_time': '09:00 AM', 'end_time': '10:00 AM', 'subject': {'id': 1, 'name': 'Operating Systems'}, 'faculty': {'id': 2, 'name': 'Prof. Kumar'}, 'room': {'id': 3, 'name': 'Lab 2'}, } ], 'upcoming_exams': [ { 'id': 1, 'name': 'Midterm Assessment', 'state': 'scheduled', 'start_date': '2026-11-01', } ], 'recent_results': [ { 'id': 1, 'exam': {'id': 1, 'name': 'Unit Test 1'}, 'percentage': 88.0, 'grade': 'A', 'status': 'pass', } ], 'announcements': [ { 'id': 1, 'name': 'College Symposium', 'date': '2026-10-20', 'summary': 'Annual tech symposium registrations open.', } ], }; final dashboard = DashboardData.fromJson(json); expect(dashboard.student?.fullName, 'Demo Student'); expect(dashboard.attendance.percentage, 92.0); expect(dashboard.todayTimetable.length, 1); expect(dashboard.todayTimetable[0].subject?.name, 'Operating Systems'); expect(dashboard.upcomingExams.length, 1); expect(dashboard.upcomingExams[0].name, 'Midterm Assessment'); expect(dashboard.recentResults.length, 1); expect(dashboard.recentResults[0].status, 'pass'); expect(dashboard.announcements.length, 1); expect(dashboard.announcements[0].name, 'College Symposium'); }); }); group('User Model Tests', () { test('parses student and non-student users', () { final studentUser = User.fromJson({ 'id': 1, 'name': 'Demo Student', 'email': 'demo.student', 'role': 'student', }); expect(studentUser.isStudent, isTrue); final adminUser = User.fromJson({ 'id': 2, 'name': 'Admin User', 'email': 'admin', 'role': 'user', }); expect(adminUser.isStudent, isFalse); }); }); group('AnnouncementItem Model Tests', () { test('parses full announcement JSON with all attributes', () { final json = { 'id': 101, 'title': 'Semester Fees Circular', 'date_published': '2026-10-01', 'expiry_date': '2026-11-01', 'summary': 'Notice to pay fees on time.', 'content': '

Notice to pay fees on time.

', 'type': 'circular', 'priority': '2', 'author': 'Finance Office', 'has_attachment': true, 'circular_filename': 'fee_structure.pdf', }; final item = AnnouncementItem.fromJson(json); expect(item.id, 101); expect(item.title, 'Semester Fees Circular'); expect(item.name, 'Semester Fees Circular'); expect(item.datePublished, '2026-10-01'); expect(item.expiryDate, '2026-11-01'); expect(item.type, 'circular'); expect(item.typeLabel, 'Circular'); expect(item.priority, '2'); expect(item.isUrgent, isTrue); expect(item.isImportant, isFalse); expect(item.priorityLabel, 'Urgent'); expect(item.author, 'Finance Office'); expect(item.hasAttachment, isTrue); expect(item.circularFilename, 'fee_structure.pdf'); }); test('handles missing, nullable, and Odoo false attributes gracefully', () { final json = { 'id': 102, 'name': 'Sports Day', 'date': '2026-10-15', 'expiry_date': false, 'type': false, 'priority': '1', 'author': false, 'has_attachment': false, 'circular_filename': null, }; final item = AnnouncementItem.fromJson(json); expect(item.id, 102); expect(item.title, 'Sports Day'); expect(item.expiryDate, isNull); expect(item.type, 'notice'); expect(item.typeLabel, 'Notice'); expect(item.priority, '1'); expect(item.isImportant, isTrue); expect(item.isUrgent, isFalse); expect(item.priorityLabel, 'Important'); expect(item.author, isNull); expect(item.hasAttachment, isFalse); expect(item.circularFilename, isNull); }); test('defaults priority label to Normal when priority is 0 or unrecognized', () { final json = {'id': 103, 'title': 'General Notice', 'priority': '0'}; final item = AnnouncementItem.fromJson(json); expect(item.priorityLabel, 'Normal'); expect(item.isUrgent, isFalse); expect(item.isImportant, isFalse); }); }); group('Fee and Finance Models Parsing Tests', () { test('parses full FeeSummary JSON', () { final json = { 'total_fees': 30000.0, 'total_paid': 10000.0, 'outstanding_balance': 20000.0, 'overdue_amount': 0.0, 'payment_status': 'partial', 'next_due_date': '2026-10-23', 'last_payment_date': '2026-10-08', 'last_receipt_number': 'REC/000001', 'currency': {'id': 20, 'name': 'INR', 'symbol': '₹'} }; final summary = FeeSummary.fromJson(json); expect(summary.totalFees, 30000.0); expect(summary.totalPaid, 10000.0); expect(summary.outstandingBalance, 20000.0); expect(summary.overdueAmount, 0.0); expect(summary.paymentStatus, 'partial'); expect(summary.nextDueDate, '2026-10-23'); expect(summary.lastPaymentDate, '2026-10-08'); expect(summary.lastReceiptNumber, 'REC/000001'); expect(summary.currencySymbol, '₹'); expect(summary.currency?.name, 'INR'); }); test('parses FeeSummary with false and nullable fields', () { final json = { 'total_fees': false, 'total_paid': null, 'outstanding_balance': '0.0', 'overdue_amount': false, 'payment_status': false, 'next_due_date': false, 'last_payment_date': null, 'last_receipt_number': false, 'currency': false, }; final summary = FeeSummary.fromJson(json); expect(summary.totalFees, 0.0); expect(summary.totalPaid, 0.0); expect(summary.outstandingBalance, 0.0); expect(summary.overdueAmount, 0.0); expect(summary.paymentStatus, 'no_dues'); expect(summary.nextDueDate, isNull); expect(summary.currencySymbol, '₹'); // default fallback }); test('parses FeeInvoice with lines and payments', () { final json = { 'id': 59, 'invoice_number': 'INV/2026/00035', 'title': 'B.Tech CS - Sem 1 Fee Structure (Demo)', 'academic_year': {'id': 10, 'name': 'Test Year'}, 'academic_term': false, 'semester': {'id': 4, 'name': 'Semester 1'}, 'assignment_date': '2026-09-23', 'due_date': '2026-10-23', 'amount': 30000.0, 'amount_paid': 10000.0, 'amount_due': 20000.0, 'payment_status': 'partial', 'state': 'confirmed', 'currency': {'id': 20, 'name': 'INR', 'symbol': '₹'}, 'lines': [ {'id': 75, 'name': 'Tuition Fee', 'category': 'Academic Fees', 'amount': 25000.0, 'mandatory': true}, {'id': 76, 'name': 'Lab Fee', 'category': 'Academic Fees', 'amount': 5000.0, 'mandatory': true} ], 'payments': [ {'id': 29, 'receipt_number': 'REC/000001', 'payment_date': '2026-10-08', 'payment_method': 'online', 'payment_method_label': 'Online Payment', 'amount': 10000.0, 'reference_number': 'DEMO-PAY-001'} ] }; final invoice = FeeInvoice.fromJson(json); expect(invoice.id, 59); expect(invoice.invoiceNumber, 'INV/2026/00035'); expect(invoice.title, 'B.Tech CS - Sem 1 Fee Structure (Demo)'); expect(invoice.academicYear?.name, 'Test Year'); expect(invoice.academicTerm, isNull); expect(invoice.semester?.name, 'Semester 1'); expect(invoice.amount, 30000.0); expect(invoice.amountPaid, 10000.0); expect(invoice.amountDue, 20000.0); expect(invoice.paymentStatus, 'partial'); expect(invoice.isPartial, isTrue); expect(invoice.isOverdue, isFalse); expect(invoice.isPaid, isFalse); expect(invoice.lines.length, 2); expect(invoice.lines[0].name, 'Tuition Fee'); expect(invoice.lines[0].amount, 25000.0); expect(invoice.payments.length, 1); expect(invoice.payments[0].receiptNumber, 'REC/000001'); expect(invoice.payments[0].amount, 10000.0); }); test('parses FeePayment record', () { final json = { 'id': 29, 'receipt_number': 'REC/000001', 'payment_date': '2026-10-08', 'payment_method': 'online', 'payment_method_label': 'Online Payment', 'amount': 10000.0, 'reference_number': 'DEMO-PAY-001', 'remarks': 'Partial Online Fee Payment', 'status': 'confirmed', 'invoice': {'id': 59, 'invoice_number': 'INV/2026/00035'}, 'currency': {'id': 20, 'name': 'INR', 'symbol': '₹'} }; final payment = FeePayment.fromJson(json); expect(payment.id, 29); expect(payment.receiptNumber, 'REC/000001'); expect(payment.paymentDate, '2026-10-08'); expect(payment.paymentMethod, 'online'); expect(payment.paymentMethodLabel, 'Online Payment'); expect(payment.amount, 10000.0); expect(payment.referenceNumber, 'DEMO-PAY-001'); expect(payment.remarks, 'Partial Online Fee Payment'); expect(payment.status, 'confirmed'); expect(payment.invoiceId, 59); expect(payment.invoiceNumber, 'INV/2026/00035'); expect(payment.currencySymbol, '₹'); }); test('parses DashboardFeeSummary inside DashboardData', () { final json = { 'attendance': {'percentage': 95.0, 'present': 19, 'absent': 1, 'late': 0, 'medical_leave': 0, 'on_duty': 0}, 'today_timetable': [], 'upcoming_exams': [], 'recent_results': [], 'announcements': [], 'fees': { 'total_fees': 30000.0, 'total_paid': 10000.0, 'outstanding_balance': 20000.0, 'overdue_amount': 0.0, 'payment_status': 'partial', 'next_due_date': '2026-10-23', 'currency_symbol': '₹' } }; final dashboard = DashboardData.fromJson(json); expect(dashboard.fees, isNotNull); expect(dashboard.fees?.outstandingBalance, 20000.0); expect(dashboard.fees?.totalPaid, 10000.0); expect(dashboard.fees?.paymentStatus, 'partial'); expect(dashboard.fees?.currencySymbol, '₹'); expect(dashboard.fees?.nextDueDate, '2026-10-23'); }); test('parses extended StudentProfile with contact, guardian, and completion fields', () { final json = { 'id': 4, 'student_id': 'STU-9999', 'admission_number': 'ADM-9999', 'registration_number': 'REG-2026-001', 'roll_number': 'ROLL-01', 'full_name': 'Demo Student', 'first_name': 'Demo', 'last_name': 'Student', 'email': 'demo.student@test.edu', 'mobile': '+919876543210', 'phone': '044-12345678', 'gender': 'male', 'date_of_birth': '2004-01-01', 'blood_group': 'o+', 'emergency_contact': 'Jane Doe', 'emergency_phone': '+919876543219', 'street': '123 College Road', 'street2': 'Apt 4B', 'city': 'Chennai', 'zip': '600001', 'state': {'id': 1, 'name': 'Tamil Nadu'}, 'country': {'id': 2, 'name': 'India'}, 'guardian': { 'name': 'John Doe', 'relation': 'Father', 'phone': '+919876543211', 'email': 'john.doe@test.com' }, 'status': 'active', 'profile_completion': 85, 'has_photo': true, 'campus': {'id': 11, 'name': 'Test Campus'}, 'department': {'id': 11, 'name': 'Test Dept'}, 'program': {'id': 11, 'name': 'Test Program'}, 'editable_fields': ['email', 'mobile', 'phone', 'blood_group'], 'protected_fields': ['student_id', 'admission_number', 'status'] }; final profile = StudentProfile.fromJson(json); expect(profile.id, 4); expect(profile.studentId, 'STU-9999'); expect(profile.admissionNumber, 'ADM-9999'); expect(profile.registrationNumber, 'REG-2026-001'); expect(profile.rollNumber, 'ROLL-01'); expect(profile.fullName, 'Demo Student'); expect(profile.email, 'demo.student@test.edu'); expect(profile.mobile, '+919876543210'); expect(profile.phone, '044-12345678'); expect(profile.bloodGroup, 'o+'); expect(profile.emergencyContact, 'Jane Doe'); expect(profile.emergencyPhone, '+919876543219'); expect(profile.street, '123 College Road'); expect(profile.city, 'Chennai'); expect(profile.zip, '600001'); expect(profile.state?.name, 'Tamil Nadu'); expect(profile.guardianName, 'John Doe'); expect(profile.guardianRelation, 'Father'); expect(profile.profileCompletion, 85); expect(profile.hasPhoto, isTrue); expect(profile.editableFields, contains('mobile')); expect(profile.protectedFields, contains('student_id')); }); test('parses StudentDocument with status flags and category labels', () { final json = { 'id': 1, 'name': 'Previous Marksheet', 'category': 'academic', 'category_label': 'Academic Certificate / Marksheet', 'file_name': 'marksheet.pdf', 'file_size': 204800, 'mime_type': 'application/pdf', 'verification_status': 'verified', 'upload_date': '2026-10-10 14:30:00', 'verification_date': '2026-10-11 10:00:00', 'rejection_reason': null }; final doc = StudentDocument.fromJson(json); expect(doc.id, 1); expect(doc.name, 'Previous Marksheet'); expect(doc.category, 'academic'); expect(doc.categoryLabel, 'Academic Certificate / Marksheet'); expect(doc.fileName, 'marksheet.pdf'); expect(doc.fileSize, 204800); expect(doc.mimeType, 'application/pdf'); expect(doc.verificationStatus, 'verified'); expect(doc.isVerified, isTrue); expect(doc.isRejected, isFalse); expect(doc.isSubmitted, isFalse); expect(doc.uploadDate, '2026-10-10 14:30:00'); }); test('parses StudentCertificate with state helpers and types', () { final json = { 'id': 1, 'name': 'CERT/2026/00001', 'certificate_type': 'bonafide', 'certificate_type_label': 'Bonafide Certificate', 'state': 'issued', 'request_date': '2026-10-10', 'issue_date': '2026-10-11', 'expiry_date': null, 'file_name': 'CERT_2026_00001.pdf', 'remarks': 'For passport application', 'rejection_reason': null, 'has_file': true }; final cert = StudentCertificate.fromJson(json); expect(cert.id, 1); expect(cert.name, 'CERT/2026/00001'); expect(cert.certificateType, 'bonafide'); expect(cert.certificateTypeLabel, 'Bonafide Certificate'); expect(cert.state, 'issued'); expect(cert.isIssued, isTrue); expect(cert.isApproved, isFalse); expect(cert.isRequested, isFalse); expect(cert.hasFile, isTrue); expect(cert.remarks, 'For passport application'); }); }); }