import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:mockito/annotations.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:lanebow/services/event_service.dart'; import 'package:lanebow/services/api_service.dart'; import 'package:lanebow/config/api_config.dart'; import 'package:lanebow/utils/test_utils.dart'; import '../utils/event_bus_test_utils.dart'; // 모킹 클래스 생성 @GenerateMocks([ApiService]) import 'event_service_test.mocks.dart'; void main() { late EventService eventService; late MockApiService mockApiService; final testToken = 'test_token'; final testClubId = 'test_club_id'; // 테스트 이벤트 데이터 final testEvent = { 'id': 'test_event_id', 'clubId': 'test_club_id', 'title': '테스트 이벤트', 'description': '테스트 설명', 'type': 'regular', 'status': 'upcoming', 'startDate': DateTime.now().toIso8601String(), 'endDate': DateTime.now().add(const Duration(hours: 2)).toIso8601String(), 'location': '테스트 장소', 'maxParticipants': 20, 'gameCount': 3, 'entryFee': 10000, 'registrationDeadline': DateTime.now().add(const Duration(days: 1)).toIso8601String(), 'isActive': true, }; // 테스트 참가자 데이터 final testParticipant = { 'id': 'test_participant_id', 'eventId': 'test_event_id', 'memberId': 'test_member_id', 'name': '테스트 참가자', 'status': 'confirmed', 'paymentStatus': 'paid', 'registrationDate': DateTime.now().toIso8601String(), }; // 테스트 점수 데이터 final testScore = { 'id': 'test_score_id', 'eventId': 'test_event_id', 'participantId': 'test_participant_id', 'participantName': '테스트 참가자', 'games': [180, 200, 220], 'handicap': 10, 'total': 610, 'average': 203.33, }; setUp(() async { // 테스트 모드 강제 설정 TestUtils.setTestMode(true); // 테스트별 고유 ID 생성 및 설정 final testId = 'event_service_test_${DateTime.now().millisecondsSinceEpoch}'; await EventBusTestUtils.setUp(testId); // SharedPreferences 모킹 SharedPreferences.setMockInitialValues({ ApiConfig.clubIdKey: testClubId, }); // API 서비스 모킹 mockApiService = MockApiService(); // EventService 초기화 (테스트용 생성자 사용) eventService = EventService.forTest(mockApiService); // 토큰 설정 await eventService.initialize(testToken); // 클럽 ID 설정 eventService.setClubId(testClubId); }); tearDown(() async { // 테스트에서 생성된 EventService 정리 try { eventService.dispose(); } catch (_) {} // EventBus 표준 정리 await EventBusTestUtils.tearDown(); // 테스트 모드 해제 TestUtils.setTestMode(false); }); group('EventService 테스트', () { test('initialize 메서드가 토큰을 설정해야 함', () async { // given // setUp에서 이미 initialize 호출됨 // then verify(mockApiService.setToken(testToken)).called(1); }); test('fetchClubEvents 메서드가 이벤트 목록을 가져와야 함', () async { // given final testEvents = [testEvent]; when(mockApiService.post( '${ApiConfig.clubs}/events', data: {'clubId': testClubId}, )).thenAnswer((_) async => testEvents); // when await eventService.fetchClubEvents(); // then verify(mockApiService.post( '${ApiConfig.clubs}/events', data: {'clubId': testClubId}, )).called(1); expect(eventService.events.length, 1); expect(eventService.events[0].id, 'test_event_id'); expect(eventService.events[0].title, '테스트 이벤트'); }); test('fetchEventById 메서드가 특정 이벤트를 가져와야 함', () async { // given final eventId = 'test_event_id'; when(mockApiService.post( '${ApiConfig.clubs}/events/detail', data: {'eventId': eventId}, )).thenAnswer((_) async => {'event': testEvent}); // when final result = await eventService.fetchEventById(eventId); // then verify(mockApiService.post( '${ApiConfig.clubs}/events/detail', data: {'eventId': eventId}, )).called(1); expect(result.id, 'test_event_id'); expect(result.title, '테스트 이벤트'); }); test('createEvent 메서드가 새 이벤트를 생성해야 함', () async { // given final newEventData = { 'clubId': testClubId, 'title': '새 이벤트', 'startDate': DateTime.now().toIso8601String(), }; when(mockApiService.post( '${ApiConfig.clubs}/events', data: newEventData, )).thenAnswer((_) async => {'event': testEvent}); // when final result = await eventService.createEvent(newEventData); // then verify(mockApiService.post( '${ApiConfig.clubs}/events', data: newEventData, )).called(1); expect(result.id, 'test_event_id'); expect(eventService.events.length, 1); }); test('updateEvent 메서드가 이벤트를 업데이트해야 함', () async { // given final eventId = 'test_event_id'; final updates = {'title': '업데이트된 이벤트'}; final updatedEvent = Map.from(testEvent); updatedEvent['title'] = '업데이트된 이벤트'; // 이벤트 목록에 테스트 이벤트 추가 when(mockApiService.post( '${ApiConfig.clubs}/events', data: {'clubId': testClubId}, )).thenAnswer((_) async => [testEvent]); await eventService.fetchClubEvents(); when(mockApiService.put( '${ApiConfig.clubs}/events/$eventId', data: updates, )).thenAnswer((_) async => {'event': updatedEvent}); // when final result = await eventService.updateEvent(eventId, updates); // then verify(mockApiService.put( '${ApiConfig.clubs}/events/$eventId', data: updates, )).called(1); expect(result.title, '업데이트된 이벤트'); expect(eventService.events[0].title, '업데이트된 이벤트'); }); test('deleteEvent 메서드가 이벤트를 삭제해야 함', () async { // given final eventId = 'test_event_id'; // 이벤트 목록에 테스트 이벤트 추가 when(mockApiService.post( '${ApiConfig.clubs}/events', data: {'clubId': testClubId}, )).thenAnswer((_) async => [testEvent]); await eventService.fetchClubEvents(); when(mockApiService.delete( '${ApiConfig.clubs}/events/$eventId', )).thenAnswer((_) async => {}); // when final result = await eventService.deleteEvent(eventId); // then verify(mockApiService.delete( '${ApiConfig.clubs}/events/$eventId', )).called(1); expect(result, true); expect(eventService.events.length, 0); }); test('fetchEventParticipants 메서드가 참가자 목록을 가져와야 함', () async { // given final eventId = 'test_event_id'; final testParticipants = [testParticipant]; when(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/participants/list', data: anyNamed('data'), )).thenAnswer((_) async => {'participants': testParticipants}); // when final result = await eventService.fetchEventParticipants(eventId); // then verify(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/participants/list', data: anyNamed('data'), )).called(1); expect(result.length, 1); expect(result[0].id, 'test_participant_id'); expect(result[0].name, '테스트 참가자'); }); test('addParticipant 메서드가 참가자를 추가해야 함', () async { // given final eventId = 'test_event_id'; final participantData = { 'memberId': 'test_member_id', 'status': 'confirmed', }; // 서비스는 clubId를 payload에 병합하므로 포함하여 검증 when(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/participants', data: anyNamed('data'), )).thenAnswer((_) async => {'participant': testParticipant}); // when final result = await eventService.addParticipant(eventId, participantData); // then verify(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/participants', data: anyNamed('data'), )).called(1); expect(result.id, 'test_participant_id'); expect(eventService.participants.length, 1); }); test('updateParticipant 메서드가 참가자 정보를 업데이트해야 함', () async { // given final eventId = 'test_event_id'; final participantId = 'test_participant_id'; final updates = {'status': 'canceled'}; final updatedParticipant = Map.from(testParticipant); updatedParticipant['status'] = 'canceled'; // 참가자 목록에 테스트 참가자 추가 when(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/participants/list', data: anyNamed('data'), )).thenAnswer((_) async => {'participants': [testParticipant]}); await eventService.fetchEventParticipants(eventId); when(mockApiService.put( '${ApiConfig.clubs}/events/$eventId/participants/$participantId', data: anyNamed('data'), )).thenAnswer((_) async => {'participant': updatedParticipant}); // when final result = await eventService.updateParticipant(eventId, participantId, updates); // then verify(mockApiService.put( '${ApiConfig.clubs}/events/$eventId/participants/$participantId', data: anyNamed('data'), )).called(1); expect(result.status, 'canceled'); expect(eventService.participants[0].status, 'canceled'); }); test('removeParticipant 메서드가 참가자를 삭제해야 함', () async { // given final eventId = 'test_event_id'; final participantId = 'test_participant_id'; // 참가자 목록에 테스트 참가자 추가 when(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/participants/list', data: anyNamed('data'), )).thenAnswer((_) async => {'participants': [testParticipant]}); await eventService.fetchEventParticipants(eventId); when(mockApiService.delete( '${ApiConfig.clubs}/events/$eventId/participants/$participantId', )).thenAnswer((_) async => {}); // when final result = await eventService.removeParticipant(eventId, participantId); // then verify(mockApiService.delete( '${ApiConfig.clubs}/events/$eventId/participants/$participantId', )).called(1); expect(result, true); expect(eventService.participants.length, 0); }); test('fetchEventScores 메서드가 점수 목록을 가져와야 함', () async { // given final eventId = 'test_event_id'; final testScores = [testScore]; when(mockApiService.post( '${ApiConfig.clubs}/events/scores', data: {'eventId': eventId}, )).thenAnswer((_) async => {'scores': testScores}); // when final result = await eventService.fetchEventScores(eventId); // then verify(mockApiService.post( '${ApiConfig.clubs}/events/scores', data: {'eventId': eventId}, )).called(1); expect(result.length, 1); expect(result[0].id, 'test_score_id'); expect(result[0].participantName, '테스트 참가자'); }); test('addScore 메서드가 점수를 추가해야 함', () async { // given final eventId = 'test_event_id'; final scoreData = { 'participantId': 'test_participant_id', 'games': [180, 200, 220], 'handicap': 10, }; when(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/scores', data: scoreData, )).thenAnswer((_) async => {'score': testScore}); // when final result = await eventService.addScore(eventId, scoreData); // then verify(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/scores', data: scoreData, )).called(1); expect(result.id, 'test_score_id'); expect(eventService.scores.length, 1); }); test('updateScore 메서드가 점수를 업데이트해야 함', () async { // given final eventId = 'test_event_id'; final scoreId = 'test_score_id'; final updates = { 'games': [190, 210, 230], 'handicap': 5, }; // 초기 점수 목록 적재 when(mockApiService.post( '${ApiConfig.clubs}/events/scores', data: {'eventId': eventId}, )).thenAnswer((_) async => {'scores': [testScore]}); await eventService.fetchEventScores(eventId); final updated = Map.from(testScore); updated['games'] = [190, 210, 230]; updated['handicap'] = 5; updated['total'] = 635; // 예시 합계 updated['average'] = 211.67; when(mockApiService.put( '${ApiConfig.clubs}/events/$eventId/scores/$scoreId', data: updates, )).thenAnswer((_) async => {'score': updated}); // when final result = await eventService.updateScore(eventId, scoreId, updates); // then verify(mockApiService.put( '${ApiConfig.clubs}/events/$eventId/scores/$scoreId', data: updates, )).called(1); expect(result.handicap, 5); expect(eventService.scores.first.handicap, 5); }); test('deleteScore 메서드가 점수를 삭제해야 함', () async { // given final eventId = 'test_event_id'; final scoreId = 'test_score_id'; // 초기 점수 목록 적재 when(mockApiService.post( '${ApiConfig.clubs}/events/scores', data: {'eventId': eventId}, )).thenAnswer((_) async => {'scores': [testScore]}); await eventService.fetchEventScores(eventId); when(mockApiService.delete( '${ApiConfig.clubs}/events/$eventId/scores/$scoreId', )).thenAnswer((_) async => {}); // when final result = await eventService.deleteScore(eventId, scoreId); // then verify(mockApiService.delete( '${ApiConfig.clubs}/events/$eventId/scores/$scoreId', )).called(1); expect(result, true); expect(eventService.scores.length, 0); }); // 실패 케이스: 참가자 추가 test('addParticipant 실패 시 예외 메시지와 로딩 상태 해제 및 목록 불변', () async { // given final eventId = 'test_event_id'; final participantData = { 'memberId': 'test_member_id', 'status': 'confirmed', }; when(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/participants', data: anyNamed('data'), )).thenThrow(Exception('API 오류')); // when expect( () => eventService.addParticipant(eventId, participantData), throwsA(isA().having((e) => e.toString(), 'message', contains('참가자 추가에 실패했습니다'))), ); // then await EventBusTestUtils.waitForIdle(); await Future.delayed(const Duration(milliseconds: 50)); expect(eventService.isLoading, false); expect(eventService.participants.length, 0); }); // 실패 케이스: 참가자 업데이트 test('updateParticipant 실패 시 예외 메시지와 로딩 상태 해제 및 목록 불변', () async { // given final eventId = 'test_event_id'; final participantId = 'test_participant_id'; final updates = {'status': 'canceled'}; // 기존 목록 적재 when(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/participants/list', data: {'clubId': testClubId, 'eventId': eventId}, )).thenAnswer((_) async => {'participants': [testParticipant]}); await eventService.fetchEventParticipants(eventId); when(mockApiService.put( '${ApiConfig.clubs}/events/$eventId/participants/$participantId', data: anyNamed('data'), )).thenThrow(Exception('API 오류')); // when expect( () => eventService.updateParticipant(eventId, participantId, updates), throwsA(isA().having((e) => e.toString(), 'message', contains('참가자 정보 업데이트에 실패했습니다'))), ); // then await EventBusTestUtils.waitForIdle(); await Future.delayed(const Duration(milliseconds: 50)); expect(eventService.isLoading, false); // 실패 시 기존 상태 유지 expect(eventService.participants.first.status, testParticipant['status']); }); // 실패 케이스: 점수 추가 test('addScore 실패 시 예외 메시지와 로딩 상태 해제 및 목록 불변', () async { // given final eventId = 'test_event_id'; final scoreData = { 'participantId': 'test_participant_id', 'games': [180, 200, 220], 'handicap': 10, }; when(mockApiService.post( '${ApiConfig.clubs}/events/$eventId/scores', data: scoreData, )).thenThrow(Exception('API 오류')); // when expect( () => eventService.addScore(eventId, scoreData), throwsA(isA().having((e) => e.toString(), 'message', contains('점수 추가에 실패했습니다'))), ); // then await EventBusTestUtils.waitForIdle(); await Future.delayed(const Duration(milliseconds: 50)); expect(eventService.isLoading, false); expect(eventService.scores.length, 0); }); // 실패 케이스: 점수 업데이트 test('updateScore 실패 시 예외 메시지와 로딩 상태 해제 및 목록 불변', () async { // given final eventId = 'test_event_id'; final scoreId = 'test_score_id'; final updates = { 'games': [190, 210, 230], 'handicap': 5, }; // 기존 목록 적재 when(mockApiService.post( '${ApiConfig.clubs}/events/scores', data: {'eventId': eventId}, )).thenAnswer((_) async => {'scores': [testScore]}); await eventService.fetchEventScores(eventId); when(mockApiService.put( '${ApiConfig.clubs}/events/$eventId/scores/$scoreId', data: updates, )).thenThrow(Exception('API 오류')); // when expect( () => eventService.updateScore(eventId, scoreId, updates), throwsA(isA().having((e) => e.toString(), 'message', contains('점수 업데이트에 실패했습니다'))), ); // then await EventBusTestUtils.waitForIdle(); // isLoading=false 수렴까지 폴링 대기 (최대 1.5s) const total = Duration(milliseconds: 1500); const step = Duration(milliseconds: 25); var waited = Duration.zero; while (eventService.isLoading && waited < total) { await Future.delayed(step); waited += step; } expect(eventService.isLoading, false); // 실패 시 기존 상태 유지 expect(eventService.scores.first.id, testScore['id']); }); }); }