Metatron_Student_ERP/flutter_app/test/models_and_parsing_test.dart

726 lines
25 KiB
Dart

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': '<p>Notice to pay fees on time.</p>',
'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');
});
});
}