이벤트 가져오기 위젯 테스트 추가 및 테스트 훅 도입
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
@@ -13,6 +12,7 @@ import 'package:lanebow/services/event_bus.dart';
|
||||
import 'package:lanebow/utils/test_utils.dart';
|
||||
|
||||
import 'club_service_integration_test.mocks.dart';
|
||||
import '../utils/event_bus_test_utils.dart';
|
||||
|
||||
@GenerateMocks([ApiService])
|
||||
void main() {
|
||||
@@ -20,11 +20,11 @@ void main() {
|
||||
late MockApiService mockApiService;
|
||||
late String testId;
|
||||
|
||||
setUp(() {
|
||||
setUp(() async {
|
||||
// 테스트 ID 생성 및 설정
|
||||
testId = 'club_service_integration_test_${DateTime.now().millisecondsSinceEpoch}';
|
||||
EventBus.setCurrentTestId(testId);
|
||||
TestUtils.setTestMode(true);
|
||||
await EventBusTestUtils.setUp(testId);
|
||||
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
SharedPreferences.setMockInitialValues({
|
||||
@@ -48,10 +48,10 @@ void main() {
|
||||
print('EventBus: 초기화됨 (테스트 ID: $testId)');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tearDown(() async {
|
||||
// 테스트 종료 후 정리
|
||||
clubService.dispose();
|
||||
EventBus.clearCurrentTestId();
|
||||
await EventBusTestUtils.tearDown();
|
||||
TestUtils.setTestMode(false);
|
||||
print('EventBus: 초기화됨 (테스트 ID: $testId)');
|
||||
});
|
||||
@@ -65,7 +65,7 @@ void main() {
|
||||
// API 응답 모킹
|
||||
when(mockApiService.setToken(token)).thenReturn(null);
|
||||
when(mockApiService.post(
|
||||
'${ApiConfig.clubs}',
|
||||
ApiConfig.clubs,
|
||||
data: {'clubId': clubId},
|
||||
)).thenAnswer((_) async => {
|
||||
'id': clubId,
|
||||
@@ -133,7 +133,7 @@ void main() {
|
||||
|
||||
// API 응답 모킹
|
||||
when(mockApiService.post(
|
||||
'${ApiConfig.clubs}',
|
||||
ApiConfig.clubs,
|
||||
data: {'clubId': clubId},
|
||||
)).thenAnswer((_) async => {
|
||||
'id': clubId,
|
||||
@@ -164,7 +164,7 @@ void main() {
|
||||
|
||||
// API 응답 모킹
|
||||
when(mockApiService.post(
|
||||
'${ApiConfig.clubs}',
|
||||
ApiConfig.clubs,
|
||||
data: {'clubId': clubId},
|
||||
)).thenAnswer((_) async => {
|
||||
'id': clubId,
|
||||
@@ -200,7 +200,7 @@ void main() {
|
||||
)).thenAnswer((_) async => {'success': true});
|
||||
|
||||
when(mockApiService.post(
|
||||
'${ApiConfig.clubs}',
|
||||
ApiConfig.clubs,
|
||||
data: {'clubId': clubId},
|
||||
)).thenAnswer((_) async => {
|
||||
'id': clubId,
|
||||
@@ -252,7 +252,7 @@ void main() {
|
||||
|
||||
// API 응답 모킹
|
||||
when(mockApiService.post(
|
||||
'${ApiConfig.clubs}',
|
||||
ApiConfig.clubs,
|
||||
data: anyNamed('data'),
|
||||
)).thenAnswer((_) async => {
|
||||
'club': {
|
||||
@@ -358,7 +358,7 @@ void main() {
|
||||
|
||||
// API 에러 모킹
|
||||
when(mockApiService.post(
|
||||
'${ApiConfig.clubs}',
|
||||
ApiConfig.clubs,
|
||||
data: {'clubId': clubId},
|
||||
)).thenThrow(Exception('API 에러 발생'));
|
||||
|
||||
@@ -388,7 +388,7 @@ void main() {
|
||||
|
||||
// API 에러 모킹
|
||||
when(mockApiService.post(
|
||||
'${ApiConfig.clubs}',
|
||||
ApiConfig.clubs,
|
||||
data: anyNamed('data'),
|
||||
)).thenThrow(Exception('클럽 생성 실패'));
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i3;
|
||||
import 'dart:async' as _i4;
|
||||
|
||||
import 'package:dio/dio.dart' as _i3;
|
||||
import 'package:lanebow/services/api_service.dart' as _i2;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
|
||||
@@ -30,6 +31,12 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void setDio(_i3.Dio? dio) => super.noSuchMethod(
|
||||
Invocation.method(#setDio, [dio]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void setToken(String? token) => super.noSuchMethod(
|
||||
Invocation.method(#setToken, [token]),
|
||||
@@ -37,7 +44,7 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> get(
|
||||
_i4.Future<dynamic> get(
|
||||
String? path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
}) =>
|
||||
@@ -47,31 +54,31 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
[path],
|
||||
{#queryParameters: queryParameters},
|
||||
),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#put, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> delete(String? path) =>
|
||||
_i4.Future<dynamic> delete(String? path) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#delete, [path]),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
}
|
||||
|
||||
@@ -6,19 +6,18 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:lanebow/services/api_service.dart';
|
||||
import 'package:lanebow/services/event_service.dart';
|
||||
import 'package:lanebow/config/api_config.dart';
|
||||
import 'package:lanebow/services/event_bus.dart';
|
||||
import 'package:lanebow/utils/test_utils.dart';
|
||||
import 'package:lanebow/models/team_model.dart';
|
||||
import 'package:lanebow/models/event_model.dart';
|
||||
|
||||
import 'event_service_integration_test.mocks.dart';
|
||||
import '../utils/event_bus_test_utils.dart';
|
||||
|
||||
@GenerateMocks([ApiService])
|
||||
void main() {
|
||||
late EventService eventService;
|
||||
late MockApiService mockApiService;
|
||||
|
||||
setUp(() {
|
||||
setUp(() async {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
SharedPreferences.setMockInitialValues({
|
||||
ApiConfig.clubIdKey: 'test_club_id' // SharedPreferences에 clubId 설정
|
||||
@@ -27,7 +26,7 @@ void main() {
|
||||
// 테스트 격리를 위한 설정
|
||||
final testId = 'event_service_integration_test_${DateTime.now().millisecondsSinceEpoch}';
|
||||
TestUtils.setTestMode(true);
|
||||
EventBus.setCurrentTestId(testId);
|
||||
await EventBusTestUtils.setUp(testId);
|
||||
|
||||
print('EventBus: 초기화됨 (테스트 ID: $testId)');
|
||||
|
||||
@@ -35,10 +34,10 @@ void main() {
|
||||
eventService = EventService.forTest(mockApiService);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tearDown(() async {
|
||||
// 테스트 종료 후 정리
|
||||
eventService.dispose();
|
||||
EventBus.clearCurrentTestId();
|
||||
await EventBusTestUtils.tearDown();
|
||||
TestUtils.setTestMode(false);
|
||||
print('EventBus: 테스트 종료');
|
||||
});
|
||||
@@ -183,8 +182,8 @@ void main() {
|
||||
|
||||
// API 응답 모킹
|
||||
when(mockApiService.post(
|
||||
'${ApiConfig.clubs}/events/participants',
|
||||
data: {'eventId': eventId},
|
||||
'${ApiConfig.clubs}/events/$eventId/participants/list',
|
||||
data: anyNamed('data'),
|
||||
)).thenAnswer((_) async => {
|
||||
'participants': [
|
||||
{
|
||||
@@ -213,8 +212,8 @@ void main() {
|
||||
expect(eventService.participants[1].name, '참가자 2');
|
||||
|
||||
verify(mockApiService.post(
|
||||
'${ApiConfig.clubs}/events/participants',
|
||||
data: {'eventId': eventId},
|
||||
'${ApiConfig.clubs}/events/$eventId/participants/list',
|
||||
data: anyNamed('data'),
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i3;
|
||||
import 'dart:async' as _i4;
|
||||
|
||||
import 'package:dio/dio.dart' as _i3;
|
||||
import 'package:lanebow/services/api_service.dart' as _i2;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
|
||||
@@ -30,6 +31,12 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void setDio(_i3.Dio? dio) => super.noSuchMethod(
|
||||
Invocation.method(#setDio, [dio]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void setToken(String? token) => super.noSuchMethod(
|
||||
Invocation.method(#setToken, [token]),
|
||||
@@ -37,7 +44,7 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> get(
|
||||
_i4.Future<dynamic> get(
|
||||
String? path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
}) =>
|
||||
@@ -47,31 +54,31 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
[path],
|
||||
{#queryParameters: queryParameters},
|
||||
),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#put, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> delete(String? path) =>
|
||||
_i4.Future<dynamic> delete(String? path) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#delete, [path]),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:lanebow/services/event_bus.dart';
|
||||
import 'package:lanebow/utils/test_utils.dart';
|
||||
|
||||
import 'member_service_integration_test.mocks.dart';
|
||||
import '../utils/event_bus_test_utils.dart';
|
||||
|
||||
@GenerateMocks([ApiService])
|
||||
void main() {
|
||||
@@ -17,11 +18,11 @@ void main() {
|
||||
late MockApiService mockApiService;
|
||||
late String testId;
|
||||
|
||||
setUp(() {
|
||||
setUp(() async {
|
||||
// 테스트 ID 생성 및 설정
|
||||
testId = 'member_service_integration_test_${DateTime.now().millisecondsSinceEpoch}';
|
||||
EventBus.setCurrentTestId(testId);
|
||||
TestUtils.setTestMode(true);
|
||||
await EventBusTestUtils.setUp(testId);
|
||||
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
SharedPreferences.setMockInitialValues({
|
||||
@@ -552,10 +553,10 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tearDown(() async {
|
||||
// 테스트 종료 후 정리
|
||||
memberService.dispose();
|
||||
EventBus.clearCurrentTestId();
|
||||
await EventBusTestUtils.tearDown();
|
||||
TestUtils.setTestMode(false);
|
||||
print('EventBus: 초기화됨 (테스트 ID: $testId)');
|
||||
});
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i3;
|
||||
import 'dart:async' as _i4;
|
||||
|
||||
import 'package:dio/dio.dart' as _i3;
|
||||
import 'package:lanebow/services/api_service.dart' as _i2;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
|
||||
@@ -30,6 +31,12 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void setDio(_i3.Dio? dio) => super.noSuchMethod(
|
||||
Invocation.method(#setDio, [dio]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void setToken(String? token) => super.noSuchMethod(
|
||||
Invocation.method(#setToken, [token]),
|
||||
@@ -37,7 +44,7 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> get(
|
||||
_i4.Future<dynamic> get(
|
||||
String? path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
}) =>
|
||||
@@ -47,31 +54,31 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
[path],
|
||||
{#queryParameters: queryParameters},
|
||||
),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#put, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> delete(String? path) =>
|
||||
_i4.Future<dynamic> delete(String? path) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#delete, [path]),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
}
|
||||
|
||||
@@ -181,8 +181,6 @@ class TestNotificationService implements NotificationService {
|
||||
|
||||
TestNotificationService(this.mockNotificationsPlugin);
|
||||
|
||||
FlutterLocalNotificationsPlugin get _notificationsPlugin => mockNotificationsPlugin;
|
||||
|
||||
@override
|
||||
bool get isInitialized => _isInitialized;
|
||||
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i3;
|
||||
import 'dart:async' as _i4;
|
||||
|
||||
import 'package:dio/dio.dart' as _i3;
|
||||
import 'package:flutter_local_notifications/src/flutter_local_notifications_plugin.dart'
|
||||
as _i4;
|
||||
import 'package:flutter_local_notifications/src/initialization_settings.dart'
|
||||
as _i5;
|
||||
import 'package:flutter_local_notifications/src/notification_details.dart'
|
||||
as _i7;
|
||||
import 'package:flutter_local_notifications/src/platform_specifics/android/schedule_mode.dart'
|
||||
as _i9;
|
||||
import 'package:flutter_local_notifications/src/types.dart' as _i10;
|
||||
import 'package:flutter_local_notifications_platform_interface/flutter_local_notifications_platform_interface.dart'
|
||||
import 'package:flutter_local_notifications/src/initialization_settings.dart'
|
||||
as _i6;
|
||||
import 'package:flutter_local_notifications/src/notification_details.dart'
|
||||
as _i8;
|
||||
import 'package:flutter_local_notifications/src/platform_specifics/android/schedule_mode.dart'
|
||||
as _i10;
|
||||
import 'package:flutter_local_notifications/src/types.dart' as _i11;
|
||||
import 'package:flutter_local_notifications_platform_interface/flutter_local_notifications_platform_interface.dart'
|
||||
as _i7;
|
||||
import 'package:lanebow/services/api_service.dart' as _i2;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:timezone/timezone.dart' as _i8;
|
||||
import 'package:timezone/timezone.dart' as _i9;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
@@ -42,6 +43,12 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void setDio(_i3.Dio? dio) => super.noSuchMethod(
|
||||
Invocation.method(#setDio, [dio]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void setToken(String? token) => super.noSuchMethod(
|
||||
Invocation.method(#setToken, [token]),
|
||||
@@ -49,7 +56,7 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> get(
|
||||
_i4.Future<dynamic> get(
|
||||
String? path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
}) =>
|
||||
@@ -59,50 +66,50 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
[path],
|
||||
{#queryParameters: queryParameters},
|
||||
),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#put, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> delete(String? path) =>
|
||||
_i4.Future<dynamic> delete(String? path) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#delete, [path]),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
}
|
||||
|
||||
/// A class which mocks [FlutterLocalNotificationsPlugin].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockFlutterLocalNotificationsPlugin extends _i1.Mock
|
||||
implements _i4.FlutterLocalNotificationsPlugin {
|
||||
implements _i5.FlutterLocalNotificationsPlugin {
|
||||
MockFlutterLocalNotificationsPlugin() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i3.Future<bool?> initialize(
|
||||
_i5.InitializationSettings? initializationSettings, {
|
||||
_i6.DidReceiveNotificationResponseCallback?
|
||||
_i4.Future<bool?> initialize(
|
||||
_i6.InitializationSettings? initializationSettings, {
|
||||
_i7.DidReceiveNotificationResponseCallback?
|
||||
onDidReceiveNotificationResponse,
|
||||
_i6.DidReceiveBackgroundNotificationResponseCallback?
|
||||
_i7.DidReceiveBackgroundNotificationResponseCallback?
|
||||
onDidReceiveBackgroundNotificationResponse,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
@@ -116,25 +123,25 @@ class MockFlutterLocalNotificationsPlugin extends _i1.Mock
|
||||
onDidReceiveBackgroundNotificationResponse,
|
||||
},
|
||||
),
|
||||
returnValue: _i3.Future<bool?>.value(),
|
||||
returnValue: _i4.Future<bool?>.value(),
|
||||
)
|
||||
as _i3.Future<bool?>);
|
||||
as _i4.Future<bool?>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i6.NotificationAppLaunchDetails?>
|
||||
_i4.Future<_i7.NotificationAppLaunchDetails?>
|
||||
getNotificationAppLaunchDetails() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getNotificationAppLaunchDetails, []),
|
||||
returnValue: _i3.Future<_i6.NotificationAppLaunchDetails?>.value(),
|
||||
returnValue: _i4.Future<_i7.NotificationAppLaunchDetails?>.value(),
|
||||
)
|
||||
as _i3.Future<_i6.NotificationAppLaunchDetails?>);
|
||||
as _i4.Future<_i7.NotificationAppLaunchDetails?>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> show(
|
||||
_i4.Future<void> show(
|
||||
int? id,
|
||||
String? title,
|
||||
String? body,
|
||||
_i7.NotificationDetails? notificationDetails, {
|
||||
_i8.NotificationDetails? notificationDetails, {
|
||||
String? payload,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
@@ -143,48 +150,48 @@ class MockFlutterLocalNotificationsPlugin extends _i1.Mock
|
||||
[id, title, body, notificationDetails],
|
||||
{#payload: payload},
|
||||
),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
as _i4.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> cancel(int? id, {String? tag}) =>
|
||||
_i4.Future<void> cancel(int? id, {String? tag}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#cancel, [id], {#tag: tag}),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
as _i4.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> cancelAll() =>
|
||||
_i4.Future<void> cancelAll() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#cancelAll, []),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
as _i4.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> cancelAllPendingNotifications() =>
|
||||
_i4.Future<void> cancelAllPendingNotifications() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#cancelAllPendingNotifications, []),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
as _i4.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> zonedSchedule(
|
||||
_i4.Future<void> zonedSchedule(
|
||||
int? id,
|
||||
String? title,
|
||||
String? body,
|
||||
_i8.TZDateTime? scheduledDate,
|
||||
_i7.NotificationDetails? notificationDetails, {
|
||||
required _i9.AndroidScheduleMode? androidScheduleMode,
|
||||
_i9.TZDateTime? scheduledDate,
|
||||
_i8.NotificationDetails? notificationDetails, {
|
||||
required _i10.AndroidScheduleMode? androidScheduleMode,
|
||||
String? payload,
|
||||
_i10.DateTimeComponents? matchDateTimeComponents,
|
||||
_i11.DateTimeComponents? matchDateTimeComponents,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
@@ -196,19 +203,19 @@ class MockFlutterLocalNotificationsPlugin extends _i1.Mock
|
||||
#matchDateTimeComponents: matchDateTimeComponents,
|
||||
},
|
||||
),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
as _i4.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> periodicallyShow(
|
||||
_i4.Future<void> periodicallyShow(
|
||||
int? id,
|
||||
String? title,
|
||||
String? body,
|
||||
_i6.RepeatInterval? repeatInterval,
|
||||
_i7.NotificationDetails? notificationDetails, {
|
||||
required _i9.AndroidScheduleMode? androidScheduleMode,
|
||||
_i7.RepeatInterval? repeatInterval,
|
||||
_i8.NotificationDetails? notificationDetails, {
|
||||
required _i10.AndroidScheduleMode? androidScheduleMode,
|
||||
String? payload,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
@@ -217,20 +224,20 @@ class MockFlutterLocalNotificationsPlugin extends _i1.Mock
|
||||
[id, title, body, repeatInterval, notificationDetails],
|
||||
{#androidScheduleMode: androidScheduleMode, #payload: payload},
|
||||
),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
as _i4.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> periodicallyShowWithDuration(
|
||||
_i4.Future<void> periodicallyShowWithDuration(
|
||||
int? id,
|
||||
String? title,
|
||||
String? body,
|
||||
Duration? repeatDurationInterval,
|
||||
_i7.NotificationDetails? notificationDetails, {
|
||||
_i9.AndroidScheduleMode? androidScheduleMode =
|
||||
_i9.AndroidScheduleMode.exact,
|
||||
_i8.NotificationDetails? notificationDetails, {
|
||||
_i10.AndroidScheduleMode? androidScheduleMode =
|
||||
_i10.AndroidScheduleMode.exact,
|
||||
String? payload,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
@@ -239,29 +246,29 @@ class MockFlutterLocalNotificationsPlugin extends _i1.Mock
|
||||
[id, title, body, repeatDurationInterval, notificationDetails],
|
||||
{#androidScheduleMode: androidScheduleMode, #payload: payload},
|
||||
),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
as _i4.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<List<_i6.PendingNotificationRequest>>
|
||||
_i4.Future<List<_i7.PendingNotificationRequest>>
|
||||
pendingNotificationRequests() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#pendingNotificationRequests, []),
|
||||
returnValue: _i3.Future<List<_i6.PendingNotificationRequest>>.value(
|
||||
<_i6.PendingNotificationRequest>[],
|
||||
returnValue: _i4.Future<List<_i7.PendingNotificationRequest>>.value(
|
||||
<_i7.PendingNotificationRequest>[],
|
||||
),
|
||||
)
|
||||
as _i3.Future<List<_i6.PendingNotificationRequest>>);
|
||||
as _i4.Future<List<_i7.PendingNotificationRequest>>);
|
||||
|
||||
@override
|
||||
_i3.Future<List<_i6.ActiveNotification>> getActiveNotifications() =>
|
||||
_i4.Future<List<_i7.ActiveNotification>> getActiveNotifications() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getActiveNotifications, []),
|
||||
returnValue: _i3.Future<List<_i6.ActiveNotification>>.value(
|
||||
<_i6.ActiveNotification>[],
|
||||
returnValue: _i4.Future<List<_i7.ActiveNotification>>.value(
|
||||
<_i7.ActiveNotification>[],
|
||||
),
|
||||
)
|
||||
as _i3.Future<List<_i6.ActiveNotification>>);
|
||||
as _i4.Future<List<_i7.ActiveNotification>>);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:lanebow/config/api_config.dart';
|
||||
import 'package:lanebow/models/score_model.dart';
|
||||
import 'package:lanebow/services/api_service.dart';
|
||||
import 'package:lanebow/services/score_service.dart';
|
||||
import 'package:lanebow/services/event_bus.dart';
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i3;
|
||||
import 'dart:async' as _i4;
|
||||
|
||||
import 'package:dio/dio.dart' as _i3;
|
||||
import 'package:lanebow/services/api_service.dart' as _i2;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
|
||||
@@ -30,6 +31,12 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void setDio(_i3.Dio? dio) => super.noSuchMethod(
|
||||
Invocation.method(#setDio, [dio]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void setToken(String? token) => super.noSuchMethod(
|
||||
Invocation.method(#setToken, [token]),
|
||||
@@ -37,7 +44,7 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> get(
|
||||
_i4.Future<dynamic> get(
|
||||
String? path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
}) =>
|
||||
@@ -47,31 +54,31 @@ class MockApiService extends _i1.Mock implements _i2.ApiService {
|
||||
[path],
|
||||
{#queryParameters: queryParameters},
|
||||
),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> post(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
_i4.Future<dynamic> put(String? path, {dynamic data}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#put, [path], {#data: data}),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i3.Future<dynamic> delete(String? path) =>
|
||||
_i4.Future<dynamic> delete(String? path) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#delete, [path]),
|
||||
returnValue: _i3.Future<dynamic>.value(),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
)
|
||||
as _i3.Future<dynamic>);
|
||||
as _i4.Future<dynamic>);
|
||||
}
|
||||
|
||||
@@ -22,21 +22,23 @@ class MockPurchaseDetails implements PurchaseDetails {
|
||||
required this.verificationData,
|
||||
required PurchaseStatus status,
|
||||
}) : _status = status;
|
||||
|
||||
|
||||
@override
|
||||
final String purchaseID;
|
||||
@override
|
||||
final String productID;
|
||||
@override
|
||||
final PurchaseVerificationData verificationData;
|
||||
|
||||
|
||||
// status 구현
|
||||
PurchaseStatus _status;
|
||||
@override
|
||||
PurchaseStatus get status => _status;
|
||||
@override
|
||||
set status(PurchaseStatus value) { _status = value; }
|
||||
|
||||
set status(PurchaseStatus value) {
|
||||
_status = value;
|
||||
}
|
||||
|
||||
@override
|
||||
IAPError? error;
|
||||
@override
|
||||
@@ -61,111 +63,121 @@ class MockPurchaseVerificationData implements PurchaseVerificationData {
|
||||
class CustomMockInAppPurchaseService implements InAppPurchaseService {
|
||||
// 내부 상태 변수
|
||||
List<ProductDetails> _products = [];
|
||||
List<PurchaseDetails> _purchases = [];
|
||||
final List<PurchaseDetails> _purchases = [];
|
||||
bool _isAvailable = true;
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
bool _isPurchaseVerified = false;
|
||||
PurchaseDetails? _lastVerifiedPurchase;
|
||||
|
||||
|
||||
// 필수 속성 구현 - 게터
|
||||
@override
|
||||
List<ProductDetails> get products => _products;
|
||||
|
||||
|
||||
@override
|
||||
List<PurchaseDetails> get purchases => _purchases;
|
||||
|
||||
|
||||
@override
|
||||
bool get isAvailable => _isAvailable;
|
||||
|
||||
|
||||
@override
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
|
||||
@override
|
||||
String? get error => _error;
|
||||
|
||||
|
||||
@override
|
||||
bool get isPurchaseVerified => _isPurchaseVerified;
|
||||
|
||||
|
||||
@override
|
||||
PurchaseDetails? get lastVerifiedPurchase => _lastVerifiedPurchase;
|
||||
|
||||
|
||||
// 필수 속성 구현 - 세터
|
||||
@override
|
||||
set isPurchaseVerified(bool value) => _isPurchaseVerified = value;
|
||||
|
||||
|
||||
@override
|
||||
set lastVerifiedPurchase(PurchaseDetails? value) {
|
||||
_lastVerifiedPurchase = value;
|
||||
if (value != null && !_purchases.contains(value)) {
|
||||
_purchases.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 필수 메서드 구현
|
||||
@override
|
||||
Future<bool> initialize() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
_isAvailable = true;
|
||||
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> purchaseSubscription(SubscriptionPlanType planType) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> restorePurchases() async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
ProductDetails? getProductByPlanType(SubscriptionPlanType planType) {
|
||||
// 테스트용 간단 구현
|
||||
return _products.isNotEmpty ? _products.first : null;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 아무것도 하지 않음 - 테스트용
|
||||
}
|
||||
|
||||
|
||||
// ChangeNotifier 구현
|
||||
final List<VoidCallback> _listeners = [];
|
||||
|
||||
|
||||
@override
|
||||
void addListener(VoidCallback listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void removeListener(VoidCallback listener) {
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
for (final listener in _listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
bool get hasListeners => _listeners.isNotEmpty;
|
||||
|
||||
|
||||
// 테스트용 설정 메서드
|
||||
void setIsPurchaseVerified(bool value) {
|
||||
_isPurchaseVerified = value;
|
||||
}
|
||||
|
||||
|
||||
void setLastVerifiedPurchase(PurchaseDetails? purchase) {
|
||||
_lastVerifiedPurchase = purchase;
|
||||
}
|
||||
|
||||
|
||||
void setProducts(List<ProductDetails> products) {
|
||||
_products = products;
|
||||
}
|
||||
|
||||
|
||||
void setError(String? error) {
|
||||
_error = error;
|
||||
}
|
||||
|
||||
|
||||
void setIsAvailable(bool value) {
|
||||
_isAvailable = value;
|
||||
}
|
||||
@@ -192,7 +204,8 @@ void main() {
|
||||
'status': 'active',
|
||||
'startDate': DateTime.now().toIso8601String(),
|
||||
'endDate': DateTime.now().add(Duration(days: 30)).toIso8601String(),
|
||||
'price': 19900.0, // double 타입으로 수정 (Subscription.fromJson에서 toDouble() 호출)
|
||||
'price':
|
||||
19900.0, // double 타입으로 수정 (Subscription.fromJson에서 toDouble() 호출)
|
||||
'currency': 'KRW',
|
||||
'autoRenew': true,
|
||||
'features': {
|
||||
@@ -202,7 +215,9 @@ void main() {
|
||||
},
|
||||
'paymentMethod': 'card',
|
||||
'transactionId': 'test_transaction_id',
|
||||
'createdAt': DateTime.now().subtract(Duration(days: 10)).toIso8601String(),
|
||||
'createdAt': DateTime.now()
|
||||
.subtract(Duration(days: 10))
|
||||
.toIso8601String(),
|
||||
'updatedAt': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
@@ -270,17 +285,21 @@ void main() {
|
||||
mockPurchaseService = CustomMockInAppPurchaseService();
|
||||
|
||||
// 현재 구독 정보 API 응답 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// 구독 플랜 목록 API 응답 설정
|
||||
when(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionPlansJson, 200));
|
||||
when(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionPlansJson, 200));
|
||||
|
||||
// 인앱 구매 서비스 초기화 설정 - when() 대신 직접 메서드 호출 준비
|
||||
// CustomMockInAppPurchaseService는 직접 구현된 클래스이므로 when() 대신 직접 설정 사용
|
||||
@@ -305,85 +324,104 @@ void main() {
|
||||
// then
|
||||
// loadCurrentSubscription의 반환값을 사용하지 않고 상태를 확인
|
||||
expect(subscriptionService.currentSubscription, isNotNull);
|
||||
expect(subscriptionService.currentSubscription!.id, 'test_subscription_id');
|
||||
expect(
|
||||
subscriptionService.currentSubscription!.id,
|
||||
'test_subscription_id',
|
||||
);
|
||||
expect(subscriptionService.isLoading, isFalse);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 플랜 목록 로드 테스트', () async {
|
||||
// given
|
||||
// 이미 setUp에서 구독 플랜 목록 API 응답이 설정되어 있음
|
||||
|
||||
|
||||
// when
|
||||
// 서비스가 생성될 때 _loadAvailablePlans()가 호출되어 이미 플랜 목록이 로드되어 있음
|
||||
|
||||
|
||||
// then
|
||||
expect(subscriptionService.availablePlans, isNotEmpty);
|
||||
// 테스트 플랜 수와 일치하는지 확인 (실제 플랜 수가 4개로 확인됨)
|
||||
|
||||
|
||||
// verify를 사용하여 HTTP 요청 확인
|
||||
verify(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).called(1);
|
||||
verify(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 복원 테스트', () async {
|
||||
// given
|
||||
// CustomMockInAppPurchaseService는 직접 구현한 클래스이므로 when() 대신 직접 설정
|
||||
mockPurchaseService.setIsPurchaseVerified(true);
|
||||
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.restoreSubscriptions();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.isLoading, isFalse);
|
||||
});
|
||||
|
||||
|
||||
test('구독 상태 검증 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정 - 정확한 URL 지정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// SubscriptionService 내부 구현에 맞게 목 설정
|
||||
// verifySubscription 메서드는 GET 요청을 사용하여 구독 상태를 확인
|
||||
when(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.verifySubscription();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('새 구독 생성 테스트', () async {
|
||||
// given
|
||||
// 인앱 구매 서비스 목 설정 - CustomMockInAppPurchaseService는 직접 구현한 클래스이므로 직접 설정
|
||||
@@ -392,116 +430,153 @@ void main() {
|
||||
MockPurchaseDetails(
|
||||
purchaseID: 'test_purchase_id',
|
||||
productID: 'test_product_id',
|
||||
verificationData: MockPurchaseVerificationData('test_verification_data'),
|
||||
verificationData: MockPurchaseVerificationData(
|
||||
'test_verification_data',
|
||||
),
|
||||
status: PurchaseStatus.purchased,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
// 서버 API 목 설정 - 정확한 API 경로와 응답 구조 사용
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.createSubscription(
|
||||
SubscriptionPlanType.premium,
|
||||
false,
|
||||
);
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
|
||||
|
||||
// CustomMockInAppPurchaseService는 mockito 객체가 아니므로 verify 대신 직접 확인
|
||||
// verify(mockPurchaseService.purchaseSubscription(SubscriptionPlanType.premium)).called(1);
|
||||
|
||||
|
||||
// HTTP 클라이언트 호출 검증
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 취소 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 서버 API 목 설정 - 실제 서비스 코드와 동일한 경로 사용
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/${testSubscription['id']}/cancel'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.subscriptions}/${testSubscription['id']}/cancel',
|
||||
),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.cancelSubscription();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/${testSubscription['id']}/cancel'),
|
||||
headers: anyNamed('headers'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.subscriptions}/${testSubscription['id']}/cancel',
|
||||
),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 플랜 변경 테스트', () async {
|
||||
// given
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 서버 API 목 설정 - 실제 서비스 코드와 동일한 경로 사용
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.changePlan}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.changePlan}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.changePlan(SubscriptionPlanType.premium, true);
|
||||
|
||||
final result = await subscriptionService.changePlan(
|
||||
SubscriptionPlanType.premium,
|
||||
true,
|
||||
);
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
});
|
||||
|
||||
|
||||
test('자동 갱신 설정 변경 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 서버 API 목 설정 - 실제 서비스 코드와 동일한 경로 사용
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.toggleAutoRenew();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -571,11 +646,13 @@ void main() {
|
||||
|
||||
test('구독 정보 로드 실패 테스트', () async {
|
||||
// given
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('서버 오류', 500));
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response('서버 오류', 500));
|
||||
|
||||
// when
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
@@ -22,21 +22,23 @@ class MockPurchaseDetails implements PurchaseDetails {
|
||||
required this.verificationData,
|
||||
required PurchaseStatus status,
|
||||
}) : _status = status;
|
||||
|
||||
|
||||
@override
|
||||
final String purchaseID;
|
||||
@override
|
||||
final String productID;
|
||||
@override
|
||||
final PurchaseVerificationData verificationData;
|
||||
|
||||
|
||||
// status 구현
|
||||
PurchaseStatus _status;
|
||||
@override
|
||||
PurchaseStatus get status => _status;
|
||||
@override
|
||||
set status(PurchaseStatus value) { _status = value; }
|
||||
|
||||
set status(PurchaseStatus value) {
|
||||
_status = value;
|
||||
}
|
||||
|
||||
@override
|
||||
IAPError? error;
|
||||
@override
|
||||
@@ -61,111 +63,121 @@ class MockPurchaseVerificationData implements PurchaseVerificationData {
|
||||
class CustomMockInAppPurchaseService implements InAppPurchaseService {
|
||||
// 내부 상태 변수
|
||||
List<ProductDetails> _products = [];
|
||||
List<PurchaseDetails> _purchases = [];
|
||||
final List<PurchaseDetails> _purchases = [];
|
||||
bool _isAvailable = true;
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
bool _isPurchaseVerified = false;
|
||||
PurchaseDetails? _lastVerifiedPurchase;
|
||||
|
||||
|
||||
// 필수 속성 구현 - 게터
|
||||
@override
|
||||
List<ProductDetails> get products => _products;
|
||||
|
||||
|
||||
@override
|
||||
List<PurchaseDetails> get purchases => _purchases;
|
||||
|
||||
|
||||
@override
|
||||
bool get isAvailable => _isAvailable;
|
||||
|
||||
|
||||
@override
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
|
||||
@override
|
||||
String? get error => _error;
|
||||
|
||||
|
||||
@override
|
||||
bool get isPurchaseVerified => _isPurchaseVerified;
|
||||
|
||||
|
||||
@override
|
||||
PurchaseDetails? get lastVerifiedPurchase => _lastVerifiedPurchase;
|
||||
|
||||
|
||||
// 필수 속성 구현 - 세터
|
||||
@override
|
||||
set isPurchaseVerified(bool value) => _isPurchaseVerified = value;
|
||||
|
||||
|
||||
@override
|
||||
set lastVerifiedPurchase(PurchaseDetails? value) {
|
||||
_lastVerifiedPurchase = value;
|
||||
if (value != null && !_purchases.contains(value)) {
|
||||
_purchases.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 필수 메서드 구현
|
||||
@override
|
||||
Future<bool> initialize() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
_isAvailable = true;
|
||||
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> purchaseSubscription(SubscriptionPlanType planType) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> restorePurchases() async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
ProductDetails? getProductByPlanType(SubscriptionPlanType planType) {
|
||||
// 테스트용 간단 구현
|
||||
return _products.isNotEmpty ? _products.first : null;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 아무것도 하지 않음 - 테스트용
|
||||
}
|
||||
|
||||
|
||||
// ChangeNotifier 구현
|
||||
final List<VoidCallback> _listeners = [];
|
||||
|
||||
|
||||
@override
|
||||
void addListener(VoidCallback listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void removeListener(VoidCallback listener) {
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
for (final listener in _listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
bool get hasListeners => _listeners.isNotEmpty;
|
||||
|
||||
|
||||
// 테스트용 설정 메서드
|
||||
void setIsPurchaseVerified(bool value) {
|
||||
_isPurchaseVerified = value;
|
||||
}
|
||||
|
||||
|
||||
void setLastVerifiedPurchase(PurchaseDetails? purchase) {
|
||||
_lastVerifiedPurchase = purchase;
|
||||
}
|
||||
|
||||
|
||||
void setProducts(List<ProductDetails> products) {
|
||||
_products = products;
|
||||
}
|
||||
|
||||
|
||||
void setError(String? error) {
|
||||
_error = error;
|
||||
}
|
||||
|
||||
|
||||
void setIsAvailable(bool value) {
|
||||
_isAvailable = value;
|
||||
}
|
||||
@@ -192,16 +204,15 @@ void main() {
|
||||
'status': 'active',
|
||||
'startDate': DateTime.now().toIso8601String(),
|
||||
'endDate': DateTime.now().add(Duration(days: 30)).toIso8601String(),
|
||||
'price': 19900, // double이 아닌 int로 변경 (toDouble() 메서드 호출 문제 해결)
|
||||
'price': 19900, // double이 아닌 int로 변경 (toDouble() 메서드 호출 문제 해결)
|
||||
'currency': 'KRW',
|
||||
'autoRenew': true,
|
||||
'features': {
|
||||
'maxMembers': 100,
|
||||
'maxEvents': 100,
|
||||
},
|
||||
'features': {'maxMembers': 100, 'maxEvents': 100},
|
||||
'paymentMethod': 'card',
|
||||
'transactionId': 'test_transaction_id',
|
||||
'createdAt': DateTime.now().subtract(Duration(days: 10)).toIso8601String(),
|
||||
'createdAt': DateTime.now()
|
||||
.subtract(Duration(days: 10))
|
||||
.toIso8601String(),
|
||||
'updatedAt': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
@@ -269,17 +280,21 @@ void main() {
|
||||
mockPurchaseService = CustomMockInAppPurchaseService();
|
||||
|
||||
// 현재 구독 정보 API 응답 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// 구독 플랜 목록 API 응답 설정
|
||||
when(mockClient.get(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/plans'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionPlansJson, 200));
|
||||
when(
|
||||
mockClient.get(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/plans'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionPlansJson, 200));
|
||||
|
||||
// 인앱 구매 서비스 초기화 설정 - when() 대신 직접 메서드 호출 준비
|
||||
// CustomMockInAppPurchaseService는 직접 구현된 클래스이므로 when() 대신 직접 설정 사용
|
||||
@@ -304,218 +319,269 @@ void main() {
|
||||
// then
|
||||
// loadCurrentSubscription의 반환값을 사용하지 않고 상태를 확인
|
||||
expect(subscriptionService.currentSubscription, isNotNull);
|
||||
expect(subscriptionService.currentSubscription!.id, 'test_subscription_id');
|
||||
expect(
|
||||
subscriptionService.currentSubscription!.id,
|
||||
'test_subscription_id',
|
||||
);
|
||||
expect(subscriptionService.isLoading, isFalse);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 플랜 목록 로드 테스트', () async {
|
||||
// given
|
||||
// 이미 setUp에서 구독 플랜 목록 API 응답이 설정되어 있음
|
||||
|
||||
|
||||
// when
|
||||
// 서비스가 생성될 때 _loadAvailablePlans()가 호출되어 이미 플랜 목록이 로드되어 있음
|
||||
|
||||
|
||||
// then
|
||||
expect(subscriptionService.availablePlans, isNotEmpty);
|
||||
// 테스트 플랜 수와 일치하는지 확인 (실제 플랜 수가 4개로 확인됨)
|
||||
|
||||
|
||||
// verify를 사용하여 HTTP 요청 확인
|
||||
verify(mockClient.get(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/plans'),
|
||||
headers: anyNamed('headers'),
|
||||
)).called(1);
|
||||
verify(
|
||||
mockClient.get(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/plans'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 복원 테스트', () async {
|
||||
// given
|
||||
when(mockPurchaseService.restorePurchases()).thenAnswer((_) async => true);
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockPurchaseService.restorePurchases(),
|
||||
).thenAnswer((_) async => true);
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.restoreSubscriptions();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.isLoading, isFalse);
|
||||
|
||||
|
||||
verify(mockPurchaseService.restorePurchases()).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 상태 검증 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
any,
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
any,
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// SubscriptionService 내부 구현에 맞게 목 설정
|
||||
// verifySubscription 메서드는 GET 요청을 사용하여 구독 상태를 확인
|
||||
when(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.verifySubscription();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.get(
|
||||
any,
|
||||
headers: anyNamed('headers'),
|
||||
)).called(1);
|
||||
|
||||
verify(mockClient.get(any, headers: anyNamed('headers'))).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('새 구독 생성 테스트', () async {
|
||||
// given
|
||||
// 인앱 구매 서비스 목 설정
|
||||
when(mockPurchaseService.purchaseSubscription(SubscriptionPlanType.premium))
|
||||
.thenAnswer((_) async => true);
|
||||
when(
|
||||
mockPurchaseService.purchaseSubscription(SubscriptionPlanType.premium),
|
||||
).thenAnswer((_) async => true);
|
||||
when(mockPurchaseService.isPurchaseVerified).thenReturn(true);
|
||||
when(mockPurchaseService.lastVerifiedPurchase).thenReturn(
|
||||
MockPurchaseDetails(
|
||||
purchaseID: 'test_purchase_id',
|
||||
productID: 'test_product_id',
|
||||
verificationData: MockPurchaseVerificationData('test_verification_data'),
|
||||
verificationData: MockPurchaseVerificationData(
|
||||
'test_verification_data',
|
||||
),
|
||||
status: PurchaseStatus.purchased,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
// 서버 API 목 설정 - 정확한 API 경로와 응답 구조 사용
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.createSubscription(
|
||||
SubscriptionPlanType.premium,
|
||||
false,
|
||||
);
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
|
||||
verify(mockPurchaseService.purchaseSubscription(SubscriptionPlanType.premium)).called(1);
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockPurchaseService.purchaseSubscription(SubscriptionPlanType.premium),
|
||||
).called(1);
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 취소 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 서버 API 목 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/${testSubscription['id']}/cancel'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.subscriptions}/${testSubscription['id']}/cancel',
|
||||
),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.cancelSubscription();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/${testSubscription['id']}/cancel'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.subscriptions}/${testSubscription['id']}/cancel',
|
||||
),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 플랜 변경 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 서버 API 목 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.changePlan(
|
||||
SubscriptionPlanType.premium,
|
||||
true,
|
||||
);
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('https://api.example.com/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('자동 갱신 설정 변경 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 서버 API 목 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.toggleAutoRenew();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -585,11 +651,13 @@ void main() {
|
||||
|
||||
test('구독 정보 로드 실패 테스트', () async {
|
||||
// given
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('{"error": "Not found"}', 404));
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response('{"error": "Not found"}', 404));
|
||||
|
||||
// when
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
@@ -20,21 +20,23 @@ class MockPurchaseDetails implements PurchaseDetails {
|
||||
required this.verificationData,
|
||||
required PurchaseStatus status,
|
||||
}) : _status = status;
|
||||
|
||||
|
||||
@override
|
||||
final String purchaseID;
|
||||
@override
|
||||
final String productID;
|
||||
@override
|
||||
final PurchaseVerificationData verificationData;
|
||||
|
||||
|
||||
// status 구현
|
||||
PurchaseStatus _status;
|
||||
@override
|
||||
PurchaseStatus get status => _status;
|
||||
@override
|
||||
set status(PurchaseStatus value) { _status = value; }
|
||||
|
||||
set status(PurchaseStatus value) {
|
||||
_status = value;
|
||||
}
|
||||
|
||||
@override
|
||||
IAPError? error;
|
||||
@override
|
||||
@@ -59,111 +61,121 @@ class MockPurchaseVerificationData implements PurchaseVerificationData {
|
||||
class CustomMockInAppPurchaseService implements InAppPurchaseService {
|
||||
// 내부 상태 변수
|
||||
List<ProductDetails> _products = [];
|
||||
List<PurchaseDetails> _purchases = [];
|
||||
final List<PurchaseDetails> _purchases = [];
|
||||
bool _isAvailable = true;
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
bool _isPurchaseVerified = false;
|
||||
PurchaseDetails? _lastVerifiedPurchase;
|
||||
|
||||
|
||||
// 필수 속성 구현 - 게터
|
||||
@override
|
||||
List<ProductDetails> get products => _products;
|
||||
|
||||
|
||||
@override
|
||||
List<PurchaseDetails> get purchases => _purchases;
|
||||
|
||||
|
||||
@override
|
||||
bool get isAvailable => _isAvailable;
|
||||
|
||||
|
||||
@override
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
|
||||
@override
|
||||
String? get error => _error;
|
||||
|
||||
|
||||
@override
|
||||
bool get isPurchaseVerified => _isPurchaseVerified;
|
||||
|
||||
|
||||
@override
|
||||
PurchaseDetails? get lastVerifiedPurchase => _lastVerifiedPurchase;
|
||||
|
||||
|
||||
// 필수 속성 구현 - 세터
|
||||
@override
|
||||
set isPurchaseVerified(bool value) => _isPurchaseVerified = value;
|
||||
|
||||
|
||||
@override
|
||||
set lastVerifiedPurchase(PurchaseDetails? value) {
|
||||
_lastVerifiedPurchase = value;
|
||||
if (value != null && !_purchases.contains(value)) {
|
||||
_purchases.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 필수 메서드 구현
|
||||
@override
|
||||
Future<bool> initialize() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
_isAvailable = true;
|
||||
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> purchaseSubscription(SubscriptionPlanType planType) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> restorePurchases() async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
ProductDetails? getProductByPlanType(SubscriptionPlanType planType) {
|
||||
// 테스트용 간단 구현
|
||||
return _products.isNotEmpty ? _products.first : null;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 아무것도 하지 않음 - 테스트용
|
||||
}
|
||||
|
||||
|
||||
// ChangeNotifier 구현
|
||||
final List<VoidCallback> _listeners = [];
|
||||
|
||||
|
||||
@override
|
||||
void addListener(VoidCallback listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void removeListener(VoidCallback listener) {
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
for (final listener in _listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
bool get hasListeners => _listeners.isNotEmpty;
|
||||
|
||||
|
||||
// 테스트용 설정 메서드
|
||||
void setIsPurchaseVerified(bool value) {
|
||||
_isPurchaseVerified = value;
|
||||
}
|
||||
|
||||
|
||||
void setLastVerifiedPurchase(PurchaseDetails? purchase) {
|
||||
_lastVerifiedPurchase = purchase;
|
||||
}
|
||||
|
||||
|
||||
void setProducts(List<ProductDetails> products) {
|
||||
_products = products;
|
||||
}
|
||||
|
||||
|
||||
void setError(String? error) {
|
||||
_error = error;
|
||||
}
|
||||
|
||||
|
||||
void setIsAvailable(bool value) {
|
||||
_isAvailable = value;
|
||||
}
|
||||
@@ -190,16 +202,15 @@ void main() {
|
||||
'status': 'active',
|
||||
'startDate': DateTime.now().toIso8601String(),
|
||||
'endDate': DateTime.now().add(Duration(days: 30)).toIso8601String(),
|
||||
'price': 19900, // double이 아닌 int로 변경 (toDouble() 메서드 호출 문제 해결)
|
||||
'price': 19900, // double이 아닌 int로 변경 (toDouble() 메서드 호출 문제 해결)
|
||||
'currency': 'KRW',
|
||||
'autoRenew': true,
|
||||
'features': {
|
||||
'maxMembers': 100,
|
||||
'maxEvents': 100,
|
||||
},
|
||||
'features': {'maxMembers': 100, 'maxEvents': 100},
|
||||
'paymentMethod': 'card',
|
||||
'transactionId': 'test_transaction_id',
|
||||
'createdAt': DateTime.now().subtract(Duration(days: 10)).toIso8601String(),
|
||||
'createdAt': DateTime.now()
|
||||
.subtract(Duration(days: 10))
|
||||
.toIso8601String(),
|
||||
'updatedAt': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
@@ -267,17 +278,21 @@ void main() {
|
||||
mockPurchaseService = CustomMockInAppPurchaseService();
|
||||
|
||||
// 현재 구독 정보 API 응답 설정 - 실제 API 엔드포인트 사용
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// 구독 플랜 목록 API 응답 설정 - 실제 API 엔드포인트 사용
|
||||
when(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionPlansJson, 200));
|
||||
when(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionPlansJson, 200));
|
||||
|
||||
// 인앱 구매 서비스 초기화 설정 - when() 대신 직접 메서드 호출
|
||||
// CustomMockInAppPurchaseService는 직접 구현된 클래스이므로 when() 대신 직접 설정 사용
|
||||
@@ -303,88 +318,105 @@ void main() {
|
||||
// then
|
||||
// loadCurrentSubscription의 반환값을 사용하지 않고 상태를 확인
|
||||
expect(subscriptionService.currentSubscription, isNotNull);
|
||||
expect(subscriptionService.currentSubscription!.id, 'test_subscription_id');
|
||||
expect(
|
||||
subscriptionService.currentSubscription!.id,
|
||||
'test_subscription_id',
|
||||
);
|
||||
expect(subscriptionService.isLoading, isFalse);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 플랜 목록 로드 테스트', () async {
|
||||
// given
|
||||
// 이미 setUp에서 구독 플랜 목록 API 응답이 설정되어 있음
|
||||
|
||||
|
||||
// when
|
||||
// 서비스가 생성될 때 _loadAvailablePlans()가 호출되어 이미 플랜 목록이 로드되어 있음
|
||||
|
||||
|
||||
// then
|
||||
expect(subscriptionService.availablePlans, isNotEmpty);
|
||||
// 테스트 플랜 수와 일치하는지 확인 (실제 플랜 수가 4개로 확인됨)
|
||||
|
||||
|
||||
// verify를 사용하여 HTTP 요청 확인 - 이미 호출되었으므로 called(1)이 아닌 called(greaterThanOrEqualTo(1)) 사용
|
||||
verify(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).called(greaterThanOrEqualTo(1));
|
||||
verify(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).called(greaterThanOrEqualTo(1));
|
||||
});
|
||||
|
||||
|
||||
test('구독 복원 테스트', () async {
|
||||
// given
|
||||
// when() 대신 직접 메서드 설정
|
||||
// mockPurchaseService.restorePurchases()는 이미 true를 반환하도록 구현되어 있음
|
||||
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.restoreSubscriptions();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.isLoading, isFalse);
|
||||
});
|
||||
|
||||
|
||||
test('구독 상태 검증 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// SubscriptionService 내부 구현에 맞게 목 설정
|
||||
// verifySubscription 메서드는 GET 요청을 사용하여 구독 상태를 확인
|
||||
// 실제 서비스 코드에서 사용하는 경로로 수정
|
||||
when(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.verifySubscription();
|
||||
|
||||
|
||||
// then
|
||||
print('구독 상태 검증 테스트 결과: $result');
|
||||
print('구독 상태 검증 테스트 오류: ${subscriptionService.error}');
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('새 구독 생성 테스트', () async {
|
||||
// given
|
||||
// 인앱 구매 서비스 목 설정 - when() 대신 직접 설정
|
||||
@@ -393,165 +425,193 @@ void main() {
|
||||
MockPurchaseDetails(
|
||||
purchaseID: 'test_purchase_id',
|
||||
productID: 'test_product_id',
|
||||
verificationData: MockPurchaseVerificationData('test_verification_data'),
|
||||
verificationData: MockPurchaseVerificationData(
|
||||
'test_verification_data',
|
||||
),
|
||||
status: PurchaseStatus.purchased,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
// API 응답 설정 - 실제 서비스 코드에서 사용하는 경로로 수정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/create'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/create'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
// createSubscription 메서드는 planType과 isYearly 두 개의 매개변수가 필요함
|
||||
final result = await subscriptionService.createSubscription(
|
||||
SubscriptionPlanType.premium,
|
||||
false, // isYearly 매개변수 추가
|
||||
);
|
||||
|
||||
|
||||
// then
|
||||
print('새 구독 생성 테스트 결과: $result');
|
||||
print('새 구독 생성 테스트 오류: ${subscriptionService.error}');
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.currentSubscription, isNotNull);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/create'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/create'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 취소 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 구독 취소 API 응답 설정 - 실제 서비스 코드에서 사용하는 경로로 수정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/test_subscription_id/cancel'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}/api/subscriptions/test_subscription_id/cancel',
|
||||
),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.cancelSubscription();
|
||||
|
||||
|
||||
// then
|
||||
print('구독 취소 테스트 결과: $result');
|
||||
print('구독 취소 테스트 오류: ${subscriptionService.error}');
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/test_subscription_id/cancel'),
|
||||
headers: anyNamed('headers'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}/api/subscriptions/test_subscription_id/cancel',
|
||||
),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 플랜 변경 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 플랜 변경 API 응답 설정 - 실제 서비스 코드에서 사용하는 경로로 수정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// 인앱 구매 서비스 목 설정 - when() 대신 직접 설정
|
||||
mockPurchaseService.setIsPurchaseVerified(true);
|
||||
|
||||
|
||||
// when
|
||||
// changePlan 메서드는 newPlanType과 isYearly 두 개의 매개변수가 필요함
|
||||
final result = await subscriptionService.changePlan(
|
||||
SubscriptionPlanType.premium,
|
||||
false, // isYearly 매개변수 추가
|
||||
);
|
||||
|
||||
|
||||
// then
|
||||
print('구독 플랜 변경 테스트 결과: $result');
|
||||
print('구독 플랜 변경 테스트 오류: ${subscriptionService.error}');
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('자동 갱신 설정 변경 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 자동 갱신 설정 변경 API 응답 설정 - 실제 서비스 코드에서 사용하는 경로로 수정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
// setAutoRenew 대신 toggleAutoRenew 메서드 사용
|
||||
final result = await subscriptionService.toggleAutoRenew();
|
||||
|
||||
|
||||
// then
|
||||
print('자동 갱신 설정 변경 테스트 결과: $result');
|
||||
print('자동 갱신 설정 변경 테스트 오류: ${subscriptionService.error}');
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
group('에러 처리 테스트', () {
|
||||
late SubscriptionService subscriptionService;
|
||||
late MockClient mockClient;
|
||||
late CustomMockInAppPurchaseService mockPurchaseService;
|
||||
|
||||
|
||||
// 테스트 사용자 ID
|
||||
const testUserId = 'test_user_id';
|
||||
const testClubId = 'test_club_id';
|
||||
const testToken = 'test_token';
|
||||
|
||||
|
||||
setUp(() {
|
||||
mockClient = MockClient();
|
||||
mockPurchaseService = CustomMockInAppPurchaseService();
|
||||
|
||||
|
||||
// 인앱 구매 서비스 초기화 설정 - when() 대신 직접 메서드 호출
|
||||
mockPurchaseService.initialize(); // 직접 호출
|
||||
|
||||
|
||||
// 구독 서비스 생성
|
||||
subscriptionService = SubscriptionService.forTest(
|
||||
client: mockClient,
|
||||
@@ -561,81 +621,97 @@ void main() {
|
||||
clubId: testClubId,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
test('구독 없는 상태에서 구독 취소 시도 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보가 없는 상태 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('{"error": "Subscription not found"}', 404));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response('{"error": "Subscription not found"}', 404),
|
||||
);
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.cancelSubscription();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isFalse);
|
||||
expect(subscriptionService.error, isNotNull);
|
||||
});
|
||||
|
||||
|
||||
test('구독 없는 상태에서 플랜 변경 시도 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보가 없는 상태 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('{"error": "Subscription not found"}', 404));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response('{"error": "Subscription not found"}', 404),
|
||||
);
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// when
|
||||
// changePlan 메서드는 newPlanType과 isYearly 두 개의 매개변수가 필요함
|
||||
final result = await subscriptionService.changePlan(
|
||||
SubscriptionPlanType.premium,
|
||||
false, // isYearly 매개변수 추가
|
||||
);
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isFalse);
|
||||
expect(subscriptionService.error, isNotNull);
|
||||
});
|
||||
|
||||
|
||||
test('구독 없는 상태에서 자동 갱신 설정 변경 시도 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보가 없는 상태 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('{"error": "Subscription not found"}', 404));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response('{"error": "Subscription not found"}', 404),
|
||||
);
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// when
|
||||
// setAutoRenew 대신 toggleAutoRenew 메서드 사용
|
||||
final result = await subscriptionService.toggleAutoRenew();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isFalse);
|
||||
expect(subscriptionService.error, isNotNull);
|
||||
});
|
||||
|
||||
|
||||
test('구독 정보 로드 실패 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 로드 실패 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('{"error": "Server error"}', 500));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response('{"error": "Server error"}', 500),
|
||||
);
|
||||
|
||||
// when
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// then
|
||||
// 404가 아닌 다른 오류 코드에서는 currentSubscription이 null이어야 함
|
||||
expect(subscriptionService.currentSubscription, isNull);
|
||||
|
||||
@@ -11,6 +11,8 @@ import 'package:lanebow/services/subscription_service.dart';
|
||||
import 'package:lanebow/config/api_config.dart';
|
||||
|
||||
import 'subscription_service_integration_test.mocks.dart';
|
||||
import 'package:lanebow/utils/test_utils.dart';
|
||||
import '../utils/event_bus_test_utils.dart';
|
||||
|
||||
// 구매 상세 정보 모킹
|
||||
class MockPurchaseDetails implements PurchaseDetails {
|
||||
@@ -20,21 +22,23 @@ class MockPurchaseDetails implements PurchaseDetails {
|
||||
required this.verificationData,
|
||||
required PurchaseStatus status,
|
||||
}) : _status = status;
|
||||
|
||||
|
||||
@override
|
||||
final String purchaseID;
|
||||
@override
|
||||
final String productID;
|
||||
@override
|
||||
final PurchaseVerificationData verificationData;
|
||||
|
||||
|
||||
// status 구현
|
||||
PurchaseStatus _status;
|
||||
@override
|
||||
PurchaseStatus get status => _status;
|
||||
@override
|
||||
set status(PurchaseStatus value) { _status = value; }
|
||||
|
||||
set status(PurchaseStatus value) {
|
||||
_status = value;
|
||||
}
|
||||
|
||||
@override
|
||||
IAPError? error;
|
||||
@override
|
||||
@@ -59,111 +63,121 @@ class MockPurchaseVerificationData implements PurchaseVerificationData {
|
||||
class CustomMockInAppPurchaseService implements InAppPurchaseService {
|
||||
// 내부 상태 변수
|
||||
List<ProductDetails> _products = [];
|
||||
List<PurchaseDetails> _purchases = [];
|
||||
final List<PurchaseDetails> _purchases = [];
|
||||
bool _isAvailable = true;
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
bool _isPurchaseVerified = false;
|
||||
PurchaseDetails? _lastVerifiedPurchase;
|
||||
|
||||
|
||||
// 필수 속성 구현 - 게터
|
||||
@override
|
||||
List<ProductDetails> get products => _products;
|
||||
|
||||
|
||||
@override
|
||||
List<PurchaseDetails> get purchases => _purchases;
|
||||
|
||||
|
||||
@override
|
||||
bool get isAvailable => _isAvailable;
|
||||
|
||||
|
||||
@override
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
|
||||
@override
|
||||
String? get error => _error;
|
||||
|
||||
|
||||
@override
|
||||
bool get isPurchaseVerified => _isPurchaseVerified;
|
||||
|
||||
|
||||
@override
|
||||
PurchaseDetails? get lastVerifiedPurchase => _lastVerifiedPurchase;
|
||||
|
||||
|
||||
// 필수 속성 구현 - 세터
|
||||
@override
|
||||
set isPurchaseVerified(bool value) => _isPurchaseVerified = value;
|
||||
|
||||
|
||||
@override
|
||||
set lastVerifiedPurchase(PurchaseDetails? value) {
|
||||
_lastVerifiedPurchase = value;
|
||||
if (value != null && !_purchases.contains(value)) {
|
||||
_purchases.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 필수 메서드 구현
|
||||
@override
|
||||
Future<bool> initialize() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
_isAvailable = true;
|
||||
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> purchaseSubscription(SubscriptionPlanType planType) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> restorePurchases() async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
ProductDetails? getProductByPlanType(SubscriptionPlanType planType) {
|
||||
// 테스트용 간단 구현
|
||||
return _products.isNotEmpty ? _products.first : null;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 아무것도 하지 않음 - 테스트용
|
||||
}
|
||||
|
||||
|
||||
// ChangeNotifier 구현
|
||||
final List<VoidCallback> _listeners = [];
|
||||
|
||||
|
||||
@override
|
||||
void addListener(VoidCallback listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void removeListener(VoidCallback listener) {
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
for (final listener in _listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
bool get hasListeners => _listeners.isNotEmpty;
|
||||
|
||||
|
||||
// 테스트용 설정 메서드
|
||||
void setIsPurchaseVerified(bool value) {
|
||||
_isPurchaseVerified = value;
|
||||
}
|
||||
|
||||
|
||||
void setLastVerifiedPurchase(PurchaseDetails? purchase) {
|
||||
_lastVerifiedPurchase = purchase;
|
||||
}
|
||||
|
||||
|
||||
void setProducts(List<ProductDetails> products) {
|
||||
_products = products;
|
||||
}
|
||||
|
||||
|
||||
void setError(String? error) {
|
||||
_error = error;
|
||||
}
|
||||
|
||||
|
||||
void setIsAvailable(bool value) {
|
||||
_isAvailable = value;
|
||||
}
|
||||
@@ -190,94 +204,131 @@ void main() {
|
||||
'status': 'active',
|
||||
'startDate': DateTime.now().toIso8601String(),
|
||||
'endDate': DateTime.now().add(Duration(days: 30)).toIso8601String(),
|
||||
'price': 19900, // double이 아닌 int로 변경 (toDouble() 메서드 호출 문제 해결)
|
||||
'price': 19900, // double이 아닌 int로 변경 (toDouble() 메서드 호출 문제 해결)
|
||||
'currency': 'KRW',
|
||||
'autoRenew': true,
|
||||
'features': {
|
||||
'maxMembers': 100,
|
||||
'maxEvents': 100,
|
||||
},
|
||||
'features': {'maxMembers': 100, 'maxEvents': 100},
|
||||
'paymentMethod': 'card',
|
||||
'transactionId': 'test_transaction_id',
|
||||
'createdAt': DateTime.now().subtract(Duration(days: 10)).toIso8601String(),
|
||||
'createdAt': DateTime.now()
|
||||
.subtract(Duration(days: 10))
|
||||
.toIso8601String(),
|
||||
'updatedAt': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
// 테스트 구독 플랜 데이터
|
||||
// 테스트 구독 플랜 데이터 (SubscriptionPlan.fromJson 스키마에 맞춤)
|
||||
final testSubscriptionPlans = [
|
||||
{
|
||||
'id': 'basic_monthly',
|
||||
'name': '베이직 월간',
|
||||
'planType': 'basic',
|
||||
'isYearly': false,
|
||||
'price': 9900,
|
||||
'type': 'basic',
|
||||
'name': 'Basic',
|
||||
'description': 'Features for small to mid clubs',
|
||||
'monthlyPrice': 9900,
|
||||
'yearlyPrice': 99000,
|
||||
'currency': 'KRW',
|
||||
'features': {
|
||||
'maxMembers': 50,
|
||||
'maxEvents': 50,
|
||||
'hasAdvancedStats': false,
|
||||
},
|
||||
'features': [
|
||||
'Manage up to 50 members',
|
||||
'Basic score records',
|
||||
'Simple stats',
|
||||
],
|
||||
'maxMembers': 50,
|
||||
'maxEvents': 50,
|
||||
'hasAdvancedStats': false,
|
||||
'hasCustomization': false,
|
||||
'hasPriority': false,
|
||||
},
|
||||
{
|
||||
'id': 'basic_yearly',
|
||||
'name': '베이직 연간',
|
||||
'planType': 'basic',
|
||||
'isYearly': true,
|
||||
'price': 99000,
|
||||
'type': 'basic',
|
||||
'name': 'Basic (Yearly)',
|
||||
'description': 'Yearly plan for small to mid clubs',
|
||||
'monthlyPrice': 9900, // monthly equivalent
|
||||
'yearlyPrice': 99000,
|
||||
'currency': 'KRW',
|
||||
'features': {
|
||||
'maxMembers': 50,
|
||||
'maxEvents': 50,
|
||||
'hasAdvancedStats': false,
|
||||
},
|
||||
'features': [
|
||||
'Manage up to 50 members',
|
||||
'Basic score records',
|
||||
'Simple stats',
|
||||
],
|
||||
'maxMembers': 50,
|
||||
'maxEvents': 50,
|
||||
'hasAdvancedStats': false,
|
||||
'hasCustomization': false,
|
||||
'hasPriority': false,
|
||||
},
|
||||
{
|
||||
'id': 'premium_monthly',
|
||||
'name': '프리미엄 월간',
|
||||
'planType': 'premium',
|
||||
'isYearly': false,
|
||||
'price': 19900,
|
||||
'type': 'premium',
|
||||
'name': 'Premium',
|
||||
'description': 'Advanced features for large clubs',
|
||||
'monthlyPrice': 19900,
|
||||
'yearlyPrice': 199000,
|
||||
'currency': 'KRW',
|
||||
'features': {
|
||||
'maxMembers': 100,
|
||||
'maxEvents': 100,
|
||||
'hasAdvancedStats': true,
|
||||
},
|
||||
'features': [
|
||||
'Unlimited members',
|
||||
'Advanced analytics',
|
||||
'Unlimited events',
|
||||
],
|
||||
'maxMembers': 100,
|
||||
'maxEvents': 100,
|
||||
'hasAdvancedStats': true,
|
||||
'hasCustomization': true,
|
||||
'hasPriority': true,
|
||||
},
|
||||
{
|
||||
'id': 'premium_yearly',
|
||||
'name': '프리미엄 연간',
|
||||
'planType': 'premium',
|
||||
'isYearly': true,
|
||||
'price': 199000,
|
||||
'type': 'premium',
|
||||
'name': 'Premium (Yearly)',
|
||||
'description': 'Yearly advanced features for large clubs',
|
||||
'monthlyPrice': 19900, // monthly equivalent
|
||||
'yearlyPrice': 199000,
|
||||
'currency': 'KRW',
|
||||
'features': {
|
||||
'maxMembers': 100,
|
||||
'maxEvents': 100,
|
||||
'hasAdvancedStats': true,
|
||||
},
|
||||
'features': [
|
||||
'Unlimited members',
|
||||
'Advanced analytics',
|
||||
'Unlimited events',
|
||||
],
|
||||
'maxMembers': 100,
|
||||
'maxEvents': 100,
|
||||
'hasAdvancedStats': true,
|
||||
'hasCustomization': true,
|
||||
'hasPriority': true,
|
||||
},
|
||||
];
|
||||
|
||||
final testSubscriptionJson = jsonEncode(testSubscription);
|
||||
final testSubscriptionPlansJson = jsonEncode(testSubscriptionPlans);
|
||||
|
||||
setUp(() {
|
||||
setUp(() async {
|
||||
// EventBus 격리 설정
|
||||
final testId =
|
||||
'subscription_service_integration_test_fixed3_${DateTime.now().millisecondsSinceEpoch}';
|
||||
TestUtils.setTestMode(true);
|
||||
await EventBusTestUtils.setUp(testId);
|
||||
mockClient = MockClient();
|
||||
mockPurchaseService = CustomMockInAppPurchaseService();
|
||||
|
||||
// 현재 구독 정보 API 응답 설정 - 실제 API 엔드포인트 사용
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
when(
|
||||
mockClient.post(
|
||||
any,
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final uri = invocation.positionalArguments.first as Uri;
|
||||
if (uri.toString().endsWith(ApiConfig.currentSubscription)) {
|
||||
return http.Response(testSubscriptionJson, 200);
|
||||
}
|
||||
return http.Response(testSubscriptionJson, 200);
|
||||
});
|
||||
|
||||
// 구독 플랜 목록 API 응답 설정 - 실제 API 엔드포인트 사용
|
||||
when(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionPlansJson, 200));
|
||||
when(mockClient.get(any, headers: anyNamed('headers'))).thenAnswer((
|
||||
invocation,
|
||||
) async {
|
||||
final uri = invocation.positionalArguments.first as Uri;
|
||||
if (uri.toString().contains(ApiConfig.subscriptionPlans)) {
|
||||
return http.Response(testSubscriptionPlansJson, 200);
|
||||
}
|
||||
return http.Response(testSubscriptionJson, 200);
|
||||
});
|
||||
|
||||
// 인앱 구매 서비스 초기화 설정 - when() 대신 직접 메서드 호출
|
||||
// CustomMockInAppPurchaseService는 직접 구현된 클래스이므로 when() 대신 직접 설정 사용
|
||||
@@ -293,6 +344,11 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await EventBusTestUtils.tearDown();
|
||||
TestUtils.setTestMode(false);
|
||||
});
|
||||
|
||||
test('현재 구독 정보 로드 테스트', () async {
|
||||
// given
|
||||
// 이미 setUp에서 현재 구독 정보 API 응답이 설정되어 있음
|
||||
@@ -303,89 +359,114 @@ void main() {
|
||||
// then
|
||||
// loadCurrentSubscription의 반환값을 사용하지 않고 상태를 확인
|
||||
expect(subscriptionService.currentSubscription, isNotNull);
|
||||
expect(subscriptionService.currentSubscription!.id, 'test_subscription_id');
|
||||
expect(
|
||||
subscriptionService.currentSubscription!.id,
|
||||
'test_subscription_id',
|
||||
);
|
||||
expect(subscriptionService.isLoading, isFalse);
|
||||
expect(subscriptionService.error, isNull);
|
||||
|
||||
verify(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).called(1);
|
||||
|
||||
verify(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
test('구독 플랜 목록 로드 테스트', () async {
|
||||
// given
|
||||
// 이미 setUp에서 구독 플랜 목록 API 응답이 설정되어 있음
|
||||
|
||||
|
||||
// when
|
||||
// 서비스가 생성될 때 _loadAvailablePlans()가 호출되어 이미 플랜 목록이 로드되어 있음
|
||||
|
||||
// 서비스 생성 시 _loadAvailablePlans()가 비동기로 호출되므로 완료를 대기
|
||||
const timeout = Duration(seconds: 2);
|
||||
final start = DateTime.now();
|
||||
while (subscriptionService.availablePlans.isEmpty &&
|
||||
DateTime.now().difference(start) < timeout) {
|
||||
await Future.delayed(const Duration(milliseconds: 20));
|
||||
}
|
||||
|
||||
// then
|
||||
expect(subscriptionService.availablePlans, isNotEmpty);
|
||||
// 테스트 플랜 수와 일치하는지 확인 (실제 플랜 수가 4개로 확인됨)
|
||||
|
||||
|
||||
// verify를 사용하여 HTTP 요청 확인 - 이미 호출되었으므로 called(1)이 아닌 called(greaterThanOrEqualTo(1)) 사용
|
||||
verify(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).called(greaterThanOrEqualTo(1));
|
||||
verify(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptionPlans}'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).called(greaterThanOrEqualTo(1));
|
||||
});
|
||||
|
||||
|
||||
test('구독 복원 테스트', () async {
|
||||
// given
|
||||
// when() 대신 직접 메서드 설정
|
||||
// mockPurchaseService.restorePurchases()는 이미 true를 반환하도록 구현되어 있음
|
||||
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.restoreSubscriptions();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.isLoading, isFalse);
|
||||
});
|
||||
|
||||
|
||||
test('구독 상태 검증 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// SubscriptionService 내부 구현에 맞게 목 설정
|
||||
// verifySubscription 메서드는 GET 요청을 사용하여 구독 상태를 확인
|
||||
// 실제 서비스 코드에서 사용하는 경로로 수정 - 중복 경로 제거
|
||||
when(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/current'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// 실제 서비스 코드에서 사용하는 정확한 경로 추가
|
||||
when(mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(jsonEncode(testSubscription), 200));
|
||||
|
||||
when(
|
||||
mockClient.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(testSubscription), 200),
|
||||
);
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.verifySubscription();
|
||||
|
||||
|
||||
// then
|
||||
print('구독 상태 검증 테스트 결과: $result');
|
||||
print('구독 상태 검증 테스트 오류: ${subscriptionService.error}');
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
});
|
||||
|
||||
|
||||
test('새 구독 생성 테스트', () async {
|
||||
// given
|
||||
// 인앱 구매 서비스 목 설정 - when() 대신 직접 설정
|
||||
@@ -394,32 +475,38 @@ void main() {
|
||||
MockPurchaseDetails(
|
||||
purchaseID: 'test_purchase_id',
|
||||
productID: 'test_product_id',
|
||||
verificationData: MockPurchaseVerificationData('test_verification_data'),
|
||||
verificationData: MockPurchaseVerificationData(
|
||||
'test_verification_data',
|
||||
),
|
||||
status: PurchaseStatus.purchased,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
// API 응답 설정 - 실제 서비스 코드에서 사용하는 경로로 수정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/create'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/create'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// 실제 서비스 코드에서 사용하는 정확한 경로 추가
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscribeClub}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
// createSubscription 메서드는 planType과 isYearly 두 개의 매개변수가 필요함
|
||||
final result = await subscriptionService.createSubscription(
|
||||
SubscriptionPlanType.premium,
|
||||
false, // isYearly 매개변수 추가
|
||||
);
|
||||
|
||||
|
||||
// then
|
||||
print('새 구독 생성 테스트 결과: $result');
|
||||
print('새 구독 생성 테스트 오류: ${subscriptionService.error}');
|
||||
@@ -427,111 +514,133 @@ void main() {
|
||||
expect(subscriptionService.currentSubscription, isNotNull);
|
||||
expect(subscriptionService.error, isNull);
|
||||
});
|
||||
|
||||
|
||||
test('구독 취소 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 구독 취소 API 응답 설정 - 실제 서비스 코드에서 사용하는 경로로 수정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/test_subscription_id/cancel'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}/api/subscriptions/test_subscription_id/cancel',
|
||||
),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// 실제 서비스 코드에서 사용하는 정확한 경로 추가
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/test_subscription_id/cancel'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.subscriptions}/test_subscription_id/cancel',
|
||||
),
|
||||
headers: anyNamed('headers'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.cancelSubscription();
|
||||
|
||||
|
||||
// then
|
||||
print('구독 취소 테스트 결과: $result');
|
||||
print('구독 취소 테스트 오류: ${subscriptionService.error}');
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
});
|
||||
|
||||
|
||||
test('구독 플랜 변경 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 플랜 변경 API 응답 설정 - 실제 서비스 코드에서 사용하는 경로로 수정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/change-plan'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// 실제 서비스 코드에서 사용하는 정확한 경로 추가 (ApiConfig.changePlan 사용)
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.changePlan}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.changePlan}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// 인앱 구매 서비스 목 설정 - when() 대신 직접 설정
|
||||
mockPurchaseService.setIsPurchaseVerified(true);
|
||||
|
||||
|
||||
// when
|
||||
// changePlan 메서드는 newPlanType과 isYearly 두 개의 매개변수가 필요함
|
||||
final result = await subscriptionService.changePlan(
|
||||
SubscriptionPlanType.premium,
|
||||
false, // isYearly 매개변수 추가
|
||||
);
|
||||
|
||||
|
||||
// then
|
||||
print('구독 플랜 변경 테스트 결과: $result');
|
||||
print('구독 플랜 변경 테스트 오류: ${subscriptionService.error}');
|
||||
expect(result, isTrue);
|
||||
expect(subscriptionService.error, isNull);
|
||||
});
|
||||
|
||||
|
||||
test('자동 갱신 설정 변경 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// 자동 갱신 설정 변경 API 응답 설정 - 실제 서비스 코드에서 사용하는 경로로 수정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}/api/subscriptions/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// 실제 서비스 코드에서 사용하는 정확한 경로 추가
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.subscriptions}/autorenew'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response(testSubscriptionJson, 200));
|
||||
|
||||
// when
|
||||
// setAutoRenew 대신 toggleAutoRenew 메서드 사용
|
||||
final result = await subscriptionService.toggleAutoRenew();
|
||||
|
||||
|
||||
// then
|
||||
print('자동 갱신 설정 변경 테스트 결과: $result');
|
||||
print('자동 갱신 설정 변경 테스트 오류: ${subscriptionService.error}');
|
||||
@@ -539,24 +648,30 @@ void main() {
|
||||
expect(subscriptionService.error, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
group('에러 처리 테스트', () {
|
||||
late SubscriptionService subscriptionService;
|
||||
late MockClient mockClient;
|
||||
late CustomMockInAppPurchaseService mockPurchaseService;
|
||||
|
||||
|
||||
// 테스트 사용자 ID
|
||||
const testUserId = 'test_user_id';
|
||||
const testClubId = 'test_club_id';
|
||||
const testToken = 'test_token';
|
||||
|
||||
setUp(() {
|
||||
|
||||
setUp(() async {
|
||||
// EventBus 격리 설정
|
||||
final testId =
|
||||
'subscription_service_integration_test_fixed3_${DateTime.now().millisecondsSinceEpoch}_error_group';
|
||||
TestUtils.setTestMode(true);
|
||||
await EventBusTestUtils.setUp(testId);
|
||||
|
||||
mockClient = MockClient();
|
||||
mockPurchaseService = CustomMockInAppPurchaseService();
|
||||
|
||||
|
||||
// 인앱 구매 서비스 초기화 설정 - when() 대신 직접 메서드 호출
|
||||
mockPurchaseService.initialize(); // 직접 호출
|
||||
|
||||
|
||||
// 구독 서비스 생성
|
||||
subscriptionService = SubscriptionService.forTest(
|
||||
client: mockClient,
|
||||
@@ -566,81 +681,102 @@ void main() {
|
||||
clubId: testClubId,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
tearDown(() async {
|
||||
await EventBusTestUtils.tearDown();
|
||||
TestUtils.setTestMode(false);
|
||||
});
|
||||
|
||||
test('구독 없는 상태에서 구독 취소 시도 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보가 없는 상태 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('{"error": "Subscription not found"}', 404));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response('{"error": "Subscription not found"}', 404),
|
||||
);
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// when
|
||||
final result = await subscriptionService.cancelSubscription();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isFalse);
|
||||
expect(subscriptionService.error, isNotNull);
|
||||
});
|
||||
|
||||
|
||||
test('구독 없는 상태에서 플랜 변경 시도 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보가 없는 상태 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('{"error": "Subscription not found"}', 404));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response('{"error": "Subscription not found"}', 404),
|
||||
);
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// when
|
||||
// changePlan 메서드는 newPlanType과 isYearly 두 개의 매개변수가 필요함
|
||||
final result = await subscriptionService.changePlan(
|
||||
SubscriptionPlanType.premium,
|
||||
false, // isYearly 매개변수 추가
|
||||
);
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isFalse);
|
||||
expect(subscriptionService.error, isNotNull);
|
||||
});
|
||||
|
||||
|
||||
test('구독 없는 상태에서 자동 갱신 설정 변경 시도 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보가 없는 상태 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('{"error": "Subscription not found"}', 404));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response('{"error": "Subscription not found"}', 404),
|
||||
);
|
||||
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// when
|
||||
// setAutoRenew 대신 toggleAutoRenew 메서드 사용
|
||||
final result = await subscriptionService.toggleAutoRenew();
|
||||
|
||||
|
||||
// then
|
||||
expect(result, isFalse);
|
||||
expect(subscriptionService.error, isNotNull);
|
||||
});
|
||||
|
||||
|
||||
test('구독 정보 로드 실패 테스트', () async {
|
||||
// given
|
||||
// 현재 구독 정보 로드 실패 설정
|
||||
when(mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
)).thenAnswer((_) async => http.Response('{"error": "Server error"}', 500));
|
||||
|
||||
when(
|
||||
mockClient.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.currentSubscription}'),
|
||||
headers: anyNamed('headers'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => http.Response('{"error": "Server error"}', 500),
|
||||
);
|
||||
|
||||
// when
|
||||
await subscriptionService.loadCurrentSubscription();
|
||||
|
||||
|
||||
// then
|
||||
// 404가 아닌 다른 오류 코드에서는 currentSubscription이 null이어야 함
|
||||
expect(subscriptionService.currentSubscription, isNull);
|
||||
|
||||
Reference in New Issue
Block a user