Files

598 lines
19 KiB
Dart

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<String, dynamic>.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<String, dynamic>.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<String, dynamic>.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<Exception>().having((e) => e.toString(), 'message', contains('참가자 추가에 실패했습니다'))),
);
// then
await EventBusTestUtils.waitForIdle();
await Future<void>.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<Exception>().having((e) => e.toString(), 'message', contains('참가자 정보 업데이트에 실패했습니다'))),
);
// then
await EventBusTestUtils.waitForIdle();
await Future<void>.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<Exception>().having((e) => e.toString(), 'message', contains('점수 추가에 실패했습니다'))),
);
// then
await EventBusTestUtils.waitForIdle();
await Future<void>.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<Exception>().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<void>.delayed(step);
waited += step;
}
expect(eventService.isLoading, false);
// 실패 시 기존 상태 유지
expect(eventService.scores.first.id, testScore['id']);
});
});
}