import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fake_cloud_firestore/fake_cloud_firestore.dart'; import 'package:firebase_auth_mocks/firebase_auth_mocks.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fancake/core/data/backend.dart'; import 'package:fancake/features/eclipse/eclipse_data.dart'; import 'package:fancake/features/eclipse/eclipse_repository_firestore.dart'; void main() { test('추가 스냅샷 없이 마감시각에 위험 상태가 끝난다', () async { final db = FakeFirebaseFirestore(); final repo = FirestoreEclipseRepository( Backend(firestore: db, auth: MockFirebaseAuth()), ); await db.doc('fandoms/bts/eclipseRounds/one').set({ 'status': 'active', 'startAt': Timestamp.now(), 'endsAt': Timestamp.fromDate( DateTime.now().add(const Duration(milliseconds: 150)), ), }); final states = []; final sub = repo.watch(fandomId: 'bts').listen(states.add); addTearDown(sub.cancel); await Future.delayed(const Duration(milliseconds: 20)); expect(states.last?.eclipseId, 'one'); await Future.delayed(const Duration(milliseconds: 200)); expect(states.last, isNull); }); test('활성 회차의 성공·종료·새 회차를 샤드 구독 중에도 반영한다', () async { final db = FakeFirebaseFirestore(); final repo = FirestoreEclipseRepository( Backend(firestore: db, auth: MockFirebaseAuth()), ); final doc = db.doc('fandoms/bts/eclipseRounds/one'); final data = { 'status': 'active', 'startAt': Timestamp.now(), 'endsAt': Timestamp.fromDate( DateTime.now().add(const Duration(hours: 1)), ), 'targetPop': 60, // 옛 필드는 목표 인원으로 재해석하지 않는다. 'target': 2, 'defended': false, }; await doc.set(data); await doc.collection('shards').doc('0').set({'defense': 600, 'defenseCount': 25, 'qualifiedParticipants': 1}); final states = []; final sub = repo.watch(fandomId: 'bts').listen(states.add); addTearDown(sub.cancel); Future flush() => Future.delayed(const Duration(milliseconds: 20)); await flush(); expect(states.last?.defense, 1); expect(states.last?.targetDefense, 2); expect(states.last?.progress, 0.5); await doc.update({'defended': true}); await flush(); expect(states.last?.defended, true); await doc.update({'status': 'ended'}); await flush(); expect(states.last, isNull); await db.doc('fandoms/bts/eclipseRounds/two').set(data); await flush(); expect(states.last?.eclipseId, 'two'); await doc.collection('shards').doc('0').update({'defense': 99}); await flush(); expect(states.last?.eclipseId, 'two'); }); test('방어 화면 구독은 마감 뒤에도 같은 회차의 서버 결과를 받는다', () async { final db = FakeFirebaseFirestore(); final repo = FirestoreEclipseRepository(Backend(firestore: db, auth: MockFirebaseAuth())); final ref = db.doc('fandoms/bts/eclipseRounds/final'); await ref.set({ 'status': 'active', 'startAt': Timestamp.now(), 'endsAt': Timestamp.fromDate(DateTime.now().add(const Duration(milliseconds: 150))), 'target': 2, 'qualifiedParticipants': 1, }); final results = []; final banner = []; final sub = repo.watch(fandomId: 'bts', includeResults: true).listen(results.add); final active = repo.watch(fandomId: 'bts').listen(banner.add); addTearDown(sub.cancel); addTearDown(active.cancel); await Future.delayed(const Duration(milliseconds: 200)); expect(banner.last, isNull); expect(results.last?.eclipseId, 'final'); expect(results.last?.canDefend, isFalse); expect(results.last?.result, isNull); await ref.update({'status': 'ended', 'result': 'failed'}); await Future.delayed(const Duration(milliseconds: 20)); expect(results.last?.result, EclipseResult.failure); expect(banner.last, isNull); await ref.update({'status': 'cancelled', 'result': 'cancelled_error'}); await Future.delayed(const Duration(milliseconds: 20)); expect(results.last?.result, EclipseResult.cancelled); }); }