446 lines
14 KiB
Dart
446 lines
14 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/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);
|
|
});
|
|
});
|
|
}
|