import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:metatron_student_erp/core/api/api_client.dart'; import 'package:metatron_student_erp/core/storage/secure_storage.dart'; import 'package:metatron_student_erp/repositories/announcement_repository.dart'; import 'package:metatron_student_erp/repositories/attendance_repository.dart'; import 'package:metatron_student_erp/repositories/auth_repository.dart'; import 'package:metatron_student_erp/repositories/exam_repository.dart'; import 'package:metatron_student_erp/repositories/fee_repository.dart'; import 'package:metatron_student_erp/repositories/result_repository.dart'; import 'package:metatron_student_erp/repositories/student_repository.dart'; import 'package:metatron_student_erp/repositories/timetable_repository.dart'; class TestSecureStorage extends SecureStorage { final Map _map = {}; TestSecureStorage() : super(); @override Future saveSessionCookie(String cookie) async { _map['session_cookie'] = cookie; } @override Future getSessionCookie() async { return _map['session_cookie']; } @override Future clearSessionCookie() async { _map.remove('session_cookie'); } @override Future clearAll() async { _map.clear(); } } void main() { group('Live Odoo Integration Test (End-to-End)', () { late ApiClient apiClient; late TestSecureStorage storage; late AuthRepository authRepo; late StudentRepository studentRepo; late AttendanceRepository attendanceRepo; late TimetableRepository timetableRepo; late ExamRepository examRepo; late ResultRepository resultRepo; late AnnouncementRepository announcementRepo; late FeeRepository feeRepo; setUpAll(() async { storage = TestSecureStorage(); apiClient = ApiClient(storage: storage); authRepo = AuthRepository(apiClient: apiClient); studentRepo = StudentRepository(apiClient: apiClient); attendanceRepo = AttendanceRepository(apiClient: apiClient); timetableRepo = TimetableRepository(apiClient: apiClient); examRepo = ExamRepository(apiClient: apiClient); resultRepo = ResultRepository(apiClient: apiClient); announcementRepo = AnnouncementRepository(apiClient: apiClient); feeRepo = FeeRepository(apiClient: apiClient); }); test('1. Flutter Login against live Odoo backend returns session and user', () async { final res = await authRepo.login( email: 'demo.student', password: 'Demo@Student2026', ); expect(res.user.email, 'demo.student'); expect(res.user.role, 'student'); expect(res.student, isNotNull); expect(res.student!.id, isPositive); final cookie = await storage.getSessionCookie(); expect(cookie, isNotNull); expect(cookie, contains('session_id=')); stdout.writeln('--> Captured Session Cookie: [REDACTED (session_id present)]'); }); test('2. GET /api/mobile/v1/auth/me returns active session', () async { final me = await authRepo.getMe(); expect(me.user.email, 'demo.student'); expect(me.student, isNotNull); stdout.writeln('--> Auth Me verified: ${me.user.name}'); }); test('3. GET /api/mobile/v1/student/profile parses student profile', () async { final profile = await studentRepo.getProfile(); expect(profile.id, isPositive); expect(profile.studentId, isNotEmpty); expect(profile.fullName, isNotEmpty); stdout.writeln('--> Profile verified: ${profile.fullName} (${profile.studentId})'); }); test('4. GET /api/mobile/v1/student/dashboard returns dashboard data', () async { final dashboard = await studentRepo.getDashboard(); expect(dashboard.attendance, isNotNull); expect(dashboard.attendance.percentage, isNonNegative); stdout.writeln('--> Dashboard verified. Attendance %: ${dashboard.attendance.percentage}'); }); test('5. GET /api/mobile/v1/student/attendance returns attendance summary', () async { final attendance = await attendanceRepo.getAttendance(); expect(attendance.summary.percentage, isNonNegative); expect(attendance.records, isNotEmpty); expect(attendance.summary.present, greaterThanOrEqualTo(3)); stdout.writeln('--> Attendance verified: ${attendance.records.length} records, percentage: ${attendance.summary.percentage}%'); }); test('6. GET /api/mobile/v1/student/timetable returns timetable slots', () async { final slots = await timetableRepo.getTimetable(); expect(slots, isA()); expect(slots.length, greaterThan(0)); final firstSlot = slots.first; expect(firstSlot.id, isPositive); expect(firstSlot.date, isNotEmpty); expect(firstSlot.startTime, isNotEmpty); expect(firstSlot.endTime, isNotEmpty); expect(firstSlot.subject, isNotNull); expect(firstSlot.subject!.name, isNotEmpty); expect(firstSlot.room, isNotNull); stdout.writeln('--> Timetable verified: ${slots.length} slots loaded with subjects and rooms'); }); test('7. GET /api/mobile/v1/student/exams returns exams list', () async { final exams = await examRepo.getExams(); expect(exams, isA()); stdout.writeln('--> Exams verified: ${exams.length} exams found'); }); test('8. GET /api/mobile/v1/student/results returns results', () async { final results = await resultRepo.getResults(); expect(results, isA()); stdout.writeln('--> Results verified: ${results.length} results found'); }); test('9. GET /api/mobile/v1/student/announcements and detail return valid notice board data', () async { final res = await announcementRepo.getAnnouncements(); expect(res.announcements, isNotEmpty); expect(res.totalCount, isPositive); final firstAnn = res.announcements.first; expect(firstAnn.id, isPositive); expect(firstAnn.title, isNotEmpty); expect(firstAnn.type, isNotEmpty); stdout.writeln('--> Announcements verified: ${res.announcements.length} loaded (first: "${firstAnn.title}")'); final detail = await announcementRepo.getAnnouncementDetail(firstAnn.id); expect(detail.id, firstAnn.id); expect(detail.title, firstAnn.title); stdout.writeln('--> Announcement detail verified: #${detail.id} "${detail.title}"'); }); test('10. GET /api/mobile/v1/student/fees/* endpoints return fee records', () async { // Summary final summary = await feeRepo.getFeeSummary(); expect(summary.totalFees, greaterThan(0)); expect(summary.totalPaid, greaterThanOrEqualTo(0)); expect(summary.outstandingBalance, isNonNegative); expect(summary.paymentStatus, isNotEmpty); stdout.writeln('--> Fee Summary verified: total=${summary.totalFees}, paid=${summary.totalPaid}, balance=${summary.outstandingBalance}, status=${summary.paymentStatus}'); // Invoices final invoicesRes = await feeRepo.getInvoices(); expect(invoicesRes.invoices, isNotEmpty); final firstInv = invoicesRes.invoices.first; expect(firstInv.id, isPositive); expect(firstInv.invoiceNumber, isNotEmpty); expect(firstInv.amount, greaterThan(0)); stdout.writeln('--> Fee Invoices verified: ${invoicesRes.invoices.length} invoices found (first: ${firstInv.invoiceNumber} - ${firstInv.title})'); // Invoice Detail final invoiceDetail = await feeRepo.getInvoiceDetail(firstInv.id); expect(invoiceDetail.id, firstInv.id); expect(invoiceDetail.invoiceNumber, firstInv.invoiceNumber); expect(invoiceDetail.lines, isNotEmpty); stdout.writeln('--> Fee Invoice Detail verified: #${invoiceDetail.id} has ${invoiceDetail.lines.length} breakdown lines and ${invoiceDetail.payments.length} payments'); // Payments final paymentsRes = await feeRepo.getPayments(); expect(paymentsRes.payments, isNotEmpty); final firstPay = paymentsRes.payments.first; expect(firstPay.id, isPositive); expect(firstPay.receiptNumber, isNotEmpty); expect(firstPay.amount, greaterThan(0)); stdout.writeln('--> Fee Payments verified: ${paymentsRes.payments.length} payments found (first: ${firstPay.receiptNumber} - amount: ${firstPay.amount})'); }); test('11. Phase 10: Profile Update, Document Upload, and Certificate Request', () async { // 1. Profile Update final updatedProfile = await studentRepo.updateProfile({ 'mobile': '+1-555-0199', 'blood_group': 'B+', 'emergency_contact': 'Jane Student', 'emergency_phone': '+1-555-0198', 'city': 'Metropolis', }); expect(updatedProfile.mobile, '+1-555-0199'); expect(updatedProfile.bloodGroup?.toUpperCase(), 'B+'); expect(updatedProfile.emergencyContact, 'Jane Student'); expect(updatedProfile.city, 'Metropolis'); expect(updatedProfile.profileCompletion, greaterThanOrEqualTo(50)); stdout.writeln('--> Profile updated successfully. Completion: ${updatedProfile.profileCompletion}%'); // 2. Document Upload final uploadedDoc = await studentRepo.uploadDocument( name: 'Birth Certificate', category: 'identity', fileContentBase64: 'JVBERi0xLjQKJcTl8uXrCg==', // Dummy base64 PDF header fileName: 'birth_cert.pdf', ); expect(uploadedDoc.id, isPositive); expect(uploadedDoc.name, 'Birth Certificate'); expect(uploadedDoc.verificationStatus, 'submitted'); stdout.writeln('--> Document uploaded: #${uploadedDoc.id} "${uploadedDoc.name}" [${uploadedDoc.verificationStatus}]'); // 3. Document Listing final docs = await studentRepo.getDocuments(); expect(docs, isNotEmpty); expect(docs.any((d) => d.id == uploadedDoc.id), isTrue); stdout.writeln('--> Document list verified: ${docs.length} documents found'); // 4. Certificate Request final requestedCert = await studentRepo.requestCertificate( certificateType: 'bonafide', remarks: 'Applying for bus pass', ); expect(requestedCert.id, isPositive); expect(requestedCert.certificateType, 'bonafide'); expect(requestedCert.state, 'requested'); stdout.writeln('--> Certificate requested: #${requestedCert.id} "${requestedCert.name}" [${requestedCert.state}]'); // 5. Certificate Listing final certs = await studentRepo.getCertificates(); expect(certs, isNotEmpty); expect(certs.any((c) => c.id == requestedCert.id), isTrue); stdout.writeln('--> Certificate list verified: ${certs.length} certificates found'); }); test('12. POST /api/mobile/v1/auth/logout ends session', () async { await authRepo.logout(); final cookie = await storage.getSessionCookie(); expect(cookie, isNull); stdout.writeln('--> Logout verified. Session cleared.'); }); }); }