598 lines
19 KiB
Dart
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']);
|
|
});
|
|
});
|
|
}
|