diff --git a/lib/core/di/cubit_registrations.dart b/lib/core/di/cubit_registrations.dart index e58aa77c..05bfd7d1 100644 --- a/lib/core/di/cubit_registrations.dart +++ b/lib/core/di/cubit_registrations.dart @@ -42,7 +42,8 @@ import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart'; import 'package:eatvio_app/features/payment/presentation/cubit/consultation_payment_cubit/consultation_payment_cubit.dart'; import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart'; import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart'; -import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans; +import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' + as profile_plans; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart'; @@ -70,16 +71,26 @@ class CubitRegistrations { /// Регистрация Cubit'ов как синглтонов static void _registerSingletonCubits() { sl.registerLazySingleton(() => NewMessagesCubit()); - sl.registerLazySingleton(() => NotificationsCubit(wsManager: sl(), notificationsRepository: sl())); - sl.registerLazySingleton(() => ProfileAvatarCubit(profileRepository: sl())); - sl.registerLazySingleton(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl())); - sl.registerLazySingleton(() => InternetConnectionCubit()); - sl.registerLazySingleton( - () => VideoCallManager( - videoCallRepository: sl(), + sl.registerLazySingleton( + () => NotificationsCubit(wsManager: sl(), notificationsRepository: sl()), + ); + sl.registerLazySingleton( + () => ProfileAvatarCubit(profileRepository: sl()), + ); + sl.registerLazySingleton( + () => ChatsCubit( + chatRepository: sl(), + wsManager: sl(), profileRepository: sl(), ), ); + sl.registerLazySingleton( + () => InternetConnectionCubit(), + ); + sl.registerLazySingleton( + () => + VideoCallManager(videoCallRepository: sl(), profileRepository: sl()), + ); sl.registerLazySingleton( () => VideoCallSessionCubit( videoCallManager: sl(), @@ -91,48 +102,130 @@ class CubitRegistrations { /// Регистрация Cubit'ов как фабрик static void _registerFactoryCubits() { // Auth related cubits - sl.registerFactory(() => AuthCubit(authRepository: sl(), firebaseApi: sl(), prefs: sl(), profileRepository: sl())); - sl.registerFactory(() => RegistrationCubit(registrationRepository: sl(), prefs: sl())); - sl.registerFactory(() => ResetPasswordCubit(resetPasswordRepository: sl())); - sl.registerFactory(() => VerifyEmailCubit(profileRepository: sl(), prefs: sl())); - sl.registerFactory(() => OAuthCubit(authRepository: sl(), prefs: sl())); - sl.registerFactory(() => RegistrationCubitV2(registrationRepository: sl())); + sl.registerFactory( + () => AuthCubit( + authRepository: sl(), + firebaseApi: sl(), + prefs: sl(), + profileRepository: sl(), + ), + ); + sl.registerFactory( + () => RegistrationCubit(registrationRepository: sl(), prefs: sl()), + ); + sl.registerFactory( + () => ResetPasswordCubit(resetPasswordRepository: sl()), + ); + sl.registerFactory( + () => VerifyEmailCubit(profileRepository: sl(), prefs: sl()), + ); + sl.registerFactory( + () => OAuthCubit(authRepository: sl(), firebaseApi: sl(), prefs: sl()), + ); + sl.registerFactory( + () => + RegistrationCubitV2(registrationRepository: sl(), firebaseApi: sl()), + ); // Feed related cubits sl.registerFactory(() => FeedCubit(feedRepository: sl())); - sl.registerFactory(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl())); - sl.registerFactory(() => DietsCubit(feedRepository: sl(), dietsRepository: sl())); - sl.registerFactory(() => PlansCubit(feedRepository: sl(), planRepository: sl())); - sl.registerFactory(() => ConsultantsCubit(feedRepository: sl())); - sl.registerFactory(() => CommentsCubit(feedRepository: sl(), commentsRepository: sl())); - sl.registerFactory(() => FeedProfileCubit(profileRepository: sl())); + sl.registerFactory( + () => RecipesCubit( + feedRepository: sl(), + recipeRepository: sl(), + aboutRecipeRepository: sl(), + ), + ); + sl.registerFactory( + () => DietsCubit(feedRepository: sl(), dietsRepository: sl()), + ); + sl.registerFactory( + () => PlansCubit(feedRepository: sl(), planRepository: sl()), + ); + sl.registerFactory( + () => ConsultantsCubit(feedRepository: sl()), + ); + sl.registerFactory( + () => CommentsCubit(feedRepository: sl(), commentsRepository: sl()), + ); + sl.registerFactory( + () => FeedProfileCubit(profileRepository: sl()), + ); // Chat related cubits - sl.registerFactory(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl())); + sl.registerFactory( + () => ChatRoomCubit( + cardsRepository: sl(), + chatRepository: sl(), + wsManager: sl(), + ), + ); // Profile related cubits - sl.registerFactory(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl())); - sl.registerFactory(() => profile_plans.PlansCubit(plansRepository: sl())); + sl.registerFactory( + () => ProfileCubit( + profileRepository: sl(), + chatRepository: sl(), + consultantRepository: sl(), + ), + ); + sl.registerFactory( + () => profile_plans.PlansCubit(plansRepository: sl()), + ); sl.registerFactory(() => SubCubit(profileRepository: sl())); // Content creation cubits - sl.registerFactory(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl())); - sl.registerFactory(() => CreatePlanCubit(createPlanRepository: sl())); - sl.registerFactory(() => CreateRecipeCubit(recipeRepository: sl(), glossaryRepository: sl())); - sl.registerFactory(() => BlogPostCreateCubit(blogPostRepository: sl())); + sl.registerFactory( + () => CreateDietCubit( + profileRepository: sl(), + createDietRepository: sl(), + glossaryRepository: sl(), + ), + ); + sl.registerFactory( + () => CreatePlanCubit(createPlanRepository: sl()), + ); + sl.registerFactory( + () => CreateRecipeCubit(recipeRepository: sl(), glossaryRepository: sl()), + ); + sl.registerFactory( + () => BlogPostCreateCubit(blogPostRepository: sl()), + ); // Content viewing cubits - sl.registerFactory(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl())); - sl.registerFactory(() => AboutPlanCubit(planRepository: sl(), profileRepository: sl(), dietRepository: sl())); - sl.registerFactory(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl())); + sl.registerFactory( + () => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()), + ); + sl.registerFactory( + () => AboutPlanCubit( + planRepository: sl(), + profileRepository: sl(), + dietRepository: sl(), + ), + ); + sl.registerFactory( + () => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()), + ); sl.registerFactory(() => BlogPostCubit(sl())); // User content cubits - sl.registerFactory(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl())); - sl.registerFactory(() => MyPlansCubit(myPlansRepository: sl())); - sl.registerFactory(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl(), recipeRepository: sl())); + sl.registerFactory( + () => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()), + ); + sl.registerFactory( + () => MyPlansCubit(myPlansRepository: sl()), + ); + sl.registerFactory( + () => MyRecipesCubit( + myRecipesRepository: sl(), + aboutRecipeRepository: sl(), + recipeRepository: sl(), + ), + ); // Other feature cubits - sl.registerFactory(() => CalendarCubit(calendarRepository: sl())); + sl.registerFactory( + () => CalendarCubit(calendarRepository: sl()), + ); sl.registerFactory( () => DiaryCubit( chatRepository: sl(), @@ -144,28 +237,73 @@ class CubitRegistrations { ), ); sl.registerFactory( - () => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl(), consultationsRepository: sl()), + () => SettingsCubit( + profileRepository: sl(), + settingsRepository: sl(), + consultantRepository: sl(), + consultationsRepository: sl(), + ), + ); + sl.registerFactory( + () => RegisterConsultantCubit(consultantRepository: sl()), + ); + sl.registerFactory( + () => ComplainingCubit(repository: sl()), + ); + sl.registerFactory( + () => UserCardsCubit(cardsRepository: sl()), + ); + sl.registerFactory( + () => OrderInfoCubit(chatRepository: sl(), profileRepository: sl()), ); - sl.registerFactory(() => RegisterConsultantCubit(consultantRepository: sl())); - sl.registerFactory(() => ComplainingCubit(repository: sl())); - sl.registerFactory(() => UserCardsCubit(cardsRepository: sl())); - sl.registerFactory(() => OrderInfoCubit(chatRepository: sl(), profileRepository: sl())); sl.registerFactory( - () => ConsultationPaymentCubit(paymentRepository: sl(), sbpRepository: sl()), + () => ConsultationPaymentCubit( + paymentRepository: sl(), + sbpRepository: sl(), + ), + ); + sl.registerFactory( + () => ConsultationsCubit(consultationsRepository: sl()), + ); + sl.registerFactory( + () => ConsultantCommentsCubit(diaryRepository: sl()), + ); + sl.registerFactory( + () => CancelPlanCubit(cancelPlanRepository: sl()), + ); + sl.registerFactory( + () => ConsultationsArchiveCubit(consultationsRepository: sl()), + ); + sl.registerFactory( + () => ConsultantFinancesCubit(consultationsRepository: sl()), + ); + sl.registerFactory( + () => UserPurchasesCubit(consultationsRepository: sl()), + ); + sl.registerFactory( + () => BlockedUsersCubit(blockedUsersRepository: sl()), + ); + sl.registerFactory( + () => NotificationSettingsCubit(notificationSettingsRepository: sl()), + ); + sl.registerFactory( + () => ConsultantRatingCubit( + feedRepository: sl(), + repository: sl(), + profileRepository: sl(), + ), ); - sl.registerFactory(() => ConsultationsCubit(consultationsRepository: sl())); - sl.registerFactory(() => ConsultantCommentsCubit(diaryRepository: sl())); - sl.registerFactory(() => CancelPlanCubit(cancelPlanRepository: sl())); - sl.registerFactory(() => ConsultationsArchiveCubit(consultationsRepository: sl())); - sl.registerFactory(() => ConsultantFinancesCubit(consultationsRepository: sl())); - sl.registerFactory(() => UserPurchasesCubit(consultationsRepository: sl())); - sl.registerFactory(() => BlockedUsersCubit(blockedUsersRepository: sl())); - sl.registerFactory(() => NotificationSettingsCubit(notificationSettingsRepository: sl())); - sl.registerFactory(() => ConsultantRatingCubit(feedRepository: sl(), repository: sl(), profileRepository: sl())); sl.registerFactory(() => ViewsCubit(viewsRepository: sl())); - sl.registerFactory(() => UserDetailsCubit(profileRepository: sl())); + sl.registerFactory( + () => UserDetailsCubit(profileRepository: sl()), + ); sl.registerFactory(() => MenuCubit(sl())); sl.registerFactory(() => MarketingCubit(sl())); - sl.registerFactory(() => PersonalDietSuggestionCubit(dietRepository: sl(), personalDietSuggestionService: sl())); + sl.registerFactory( + () => PersonalDietSuggestionCubit( + dietRepository: sl(), + personalDietSuggestionService: sl(), + ), + ); } } diff --git a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart index 45d00f13..f6f27797 100644 --- a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart +++ b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart @@ -1,5 +1,6 @@ import 'dart:developer'; import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/firebase_api.dart'; import 'package:eatvio_app/core/mixins/registration_date_cache_mixin.dart'; import 'package:eatvio_app/core/prefs.dart'; import 'package:eatvio_app/core/token_storage.dart'; @@ -11,12 +12,22 @@ import 'package:eatvio_app/core/error_reporting/report_from.dart'; class OAuthCubit extends Cubit with RegistrationDateCacheMixin { final AuthRepository authRepository; + final FirebaseApi firebaseApi; - OAuthCubit({required this.authRepository, required Prefs prefs}) : super(const OAuthState()) { + OAuthCubit({ + required this.authRepository, + required this.firebaseApi, + required Prefs prefs, + }) : super(const OAuthState()) { this.prefs = prefs; } - Future signInViaSocialMedia({required String provider, required String accessToken, required String refreshToken, required String clientId}) async { + Future signInViaSocialMedia({ + required String provider, + required String accessToken, + required String refreshToken, + required String clientId, + }) async { try { emit(const OAuthState.loading()); @@ -28,7 +39,7 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { ); log('OAuthCubit: Got token model: ${tokenModel.token}'); - TokenStorage.saveTokenInStorage(tokenModel.token); + await TokenStorage.saveTokenInStorage(tokenModel.token); emit(OAuthState.loaded(token: tokenModel.token)); } on DioException catch (e, st) { await reportFrom(e, st, this); @@ -45,7 +56,8 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { if (errorStr.contains('Некорректный формат email')) { errorsList.add(RegistrationValidationErrors.incorrectEmailFormat); } - if (errorStr.contains('почта уже существует') || errorStr.contains('Эл. почта уже существует')) { + if (errorStr.contains('почта уже существует') || + errorStr.contains('Эл. почта уже существует')) { errorsList.add(RegistrationValidationErrors.emailExist); } } @@ -53,14 +65,20 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { if (phoneErrors != null && phoneErrors.isNotEmpty) { for (final error in phoneErrors) { final errorStr = error.toString(); - if (errorStr.contains('phone уже существует') || errorStr.contains('телефон уже существует')) { + if (errorStr.contains('phone уже существует') || + errorStr.contains('телефон уже существует')) { errorsList.add(RegistrationValidationErrors.phoneExist); } } } if (errorsList.isNotEmpty) { - emit(OAuthState.error(errorMessage: 'validation_error', registrationValidationErrors: errorsList)); + emit( + OAuthState.error( + errorMessage: 'validation_error', + registrationValidationErrors: errorsList, + ), + ); } else { emit(OAuthState.error(errorMessage: e.toString())); } @@ -103,7 +121,8 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { height: height, clientId: clientId, ); - TokenStorage.saveTokenInStorage(token); + await TokenStorage.saveTokenInStorage(token); + await firebaseApi.registerPushToken(); cacheRegistrationDate(DateTime.now()); emit(OAuthState.loaded(token: token)); } on DioException catch (e, st) { @@ -120,7 +139,8 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { if (errorStr.contains('Некорректный формат email')) { errorsList.add(RegistrationValidationErrors.incorrectEmailFormat); } - if (errorStr.contains('почта уже существует') || errorStr.contains('Эл. почта уже существует')) { + if (errorStr.contains('почта уже существует') || + errorStr.contains('Эл. почта уже существует')) { errorsList.add(RegistrationValidationErrors.emailExist); } } @@ -128,14 +148,17 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { if (phoneErrors != null && phoneErrors.isNotEmpty) { for (final error in phoneErrors) { final errorStr = error.toString(); - if (errorStr.contains('phone уже существует') || errorStr.contains('телефон уже существует')) { + if (errorStr.contains('phone уже существует') || + errorStr.contains('телефон уже существует')) { errorsList.add(RegistrationValidationErrors.phoneExist); } } } if (dateOfBirth != null && dateOfBirth.isNotEmpty) { if (dateOfBirth.contains('Некорректный формат даты рождения')) { - errorsList.add(RegistrationValidationErrors.incorrectDateOfBirthFormat); + errorsList.add( + RegistrationValidationErrors.incorrectDateOfBirthFormat, + ); } } if (errorsList.isNotEmpty) { @@ -146,13 +169,23 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { return null; } - Future requestOAuthVerification({String? email, String? phone}) async { + Future requestOAuthVerification({ + String? email, + String? phone, + }) async { try { emit(const OAuthState.loading()); - log('OAuthCubit: Requesting verification code for email: $email, phone: $phone'); - final response = await authRepository.requestOAuthVerification(email: email, phone: phone); + log( + 'OAuthCubit: Requesting verification code for email: $email, phone: $phone', + ); + final response = await authRepository.requestOAuthVerification( + email: email, + phone: phone, + ); if (response != null) { - log('OAuthCubit: Verification code requested successfully, masked email: ${response.email}'); + log( + 'OAuthCubit: Verification code requested successfully, masked email: ${response.email}', + ); } // Сбрасываем состояние после успешного запроса кода emit(const OAuthState()); @@ -184,7 +217,9 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { }) async { try { emit(const OAuthState.loading()); - log('OAuthCubit: Confirming OAuth verification - provider: $provider, verificationType: $verificationType, code: $code'); + log( + 'OAuthCubit: Confirming OAuth verification - provider: $provider, verificationType: $verificationType, code: $code', + ); final response = await authRepository.confirmOAuthVerification( provider: provider, accessToken: accessToken, @@ -202,8 +237,11 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { weight: weight, height: height, ); - log('OAuthCubit: OAuth verification confirmed successfully, token received'); - TokenStorage.saveTokenInStorage(response.token); + log( + 'OAuthCubit: OAuth verification confirmed successfully, token received', + ); + await TokenStorage.saveTokenInStorage(response.token); + await firebaseApi.registerPushToken(); emit(OAuthState.loaded(token: response.token)); } catch (e, st) { await reportFrom(e, st, this); @@ -212,4 +250,3 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { } } } - diff --git a/lib/features/registration_v2/cubit/registration_cubit.dart b/lib/features/registration_v2/cubit/registration_cubit.dart index 585d1c41..8dee14d3 100644 --- a/lib/features/registration_v2/cubit/registration_cubit.dart +++ b/lib/features/registration_v2/cubit/registration_cubit.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/firebase_api.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/core/utils/validation_utils.dart'; @@ -13,7 +14,11 @@ import 'package:gpassword/gpassword.dart'; class RegistrationCubitV2 extends Cubit { final RegistrationRepositoryV2 registrationRepository; - RegistrationCubitV2({required this.registrationRepository}) : super(const RegistrationStateV2()); + final FirebaseApi firebaseApi; + RegistrationCubitV2({ + required this.registrationRepository, + required this.firebaseApi, + }) : super(const RegistrationStateV2()); void setFirstName(String firstName) { emit(state.copyWith(firstName: firstName)); @@ -194,9 +199,12 @@ class RegistrationCubitV2 extends Cubit { bool didApiComplainOnValidation = false; String errorMessage = ''; try { - final token = await registrationRepository.register(registrationModel: registrationModel); + final token = await registrationRepository.register( + registrationModel: registrationModel, + ); if (token != null) { - TokenStorage.saveTokenInStorage(token); + await TokenStorage.saveTokenInStorage(token); + await firebaseApi.registerPushToken(); emit(state.copyWith(registrationError: null)); return (true, false); } @@ -214,11 +222,21 @@ class RegistrationCubitV2 extends Cubit { for (var error in emailErrors) { allErrors.add(error.toString()); if (error.toString() == 'Некорректный формат email') { - emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Некорректный формат email')); + emit( + state.copyWith( + isEmailValid: false, + emailDisplayError: 'Некорректный формат email', + ), + ); didApiComplainOnValidation = true; } if (error.toString() == 'Такой Эл. почта уже существует.') { - emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Такая Эл. почта уже существует.')); + emit( + state.copyWith( + isEmailValid: false, + emailDisplayError: 'Такая Эл. почта уже существует.', + ), + ); didApiComplainOnValidation = true; } } @@ -227,7 +245,12 @@ class RegistrationCubitV2 extends Cubit { for (var error in phoneErrors) { allErrors.add(error.toString()); if (error.toString() == 'Такой phone уже существует.') { - emit(state.copyWith(isPhoneValid: false, phoneDisplayError: 'Такой номер телефона уже существует.')); + emit( + state.copyWith( + isPhoneValid: false, + phoneDisplayError: 'Такой номер телефона уже существует.', + ), + ); didApiComplainOnValidation = true; } } @@ -236,7 +259,12 @@ class RegistrationCubitV2 extends Cubit { for (var error in dateOfBirth) { allErrors.add(error.toString()); if (error.toString() == 'Некорректный формат даты рождения') { - emit(state.copyWith(isBirthdayValid: false, dateOfBirthDisplayError: 'Некорректный формат даты рождения')); + emit( + state.copyWith( + isBirthdayValid: false, + dateOfBirthDisplayError: 'Некорректный формат даты рождения', + ), + ); didApiComplainOnValidation = true; } } diff --git a/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart b/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart index 94401422..3f757bb1 100644 --- a/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart +++ b/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/firebase_api.dart'; import 'package:eatvio_app/core/prefs.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_response.dart'; @@ -16,6 +17,17 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../../../../../fixtures/fixture_reader.dart'; import 'oauth_cubit_test.mocks.dart'; +class _RecordingFirebaseApi extends Fake implements FirebaseApi { + int registerPushTokenCalls = 0; + bool hadAuthToken = false; + + @override + Future registerPushToken() async { + registerPushTokenCalls++; + hadAuthToken = await TokenStorage.haveTokenInStorage(); + } +} + @GenerateNiceMocks([MockSpec(), MockSpec()]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -24,26 +36,36 @@ void main() { OAuthCubit? oauthCubit; late MockAuthRepository mockAuthRepository; late MockPrefs mockPrefs; + late _RecordingFirebaseApi firebaseApi; late TokenModel tokenFixture; late Map signInRequestFixture; late Map signUpRequestFixture; setUp(() async { - tokenFixture = TokenModel.fromJson(FixtureReader.readAsMap('auth/token_response.json')); - signInRequestFixture = FixtureReader.readAsMap('auth/oauth_signin_request.json'); - signUpRequestFixture = FixtureReader.readAsMap('auth/oauth_signup_request.json'); + tokenFixture = TokenModel.fromJson( + FixtureReader.readAsMap('auth/token_response.json'), + ); + signInRequestFixture = FixtureReader.readAsMap( + 'auth/oauth_signin_request.json', + ); + signUpRequestFixture = FixtureReader.readAsMap( + 'auth/oauth_signup_request.json', + ); mockAuthRepository = MockAuthRepository(); mockPrefs = MockPrefs(); + firebaseApi = _RecordingFirebaseApi(); SharedPreferences.setMockInitialValues({}); // Mock Fluttertoast plugin - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('PonnamKarthik/fluttertoast'), ( - MethodCall methodCall, - ) async { - return null; - }); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('PonnamKarthik/fluttertoast'), + (MethodCall methodCall) async { + return null; + }, + ); }); tearDown(() async { @@ -52,18 +74,31 @@ void main() { oauthCubit!.close(); oauthCubit = null; } + TokenStorage.clearCache(); // Clear mock handlers - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('PonnamKarthik/fluttertoast'), null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('PonnamKarthik/fluttertoast'), + null, + ); }); test('начальное состояние - OAuthState', () { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); expect(oauthCubit!.state, const OAuthState()); }); test('signInViaSocialMedia - успешный вход через соцсеть', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); when( mockAuthRepository.signInViaSocialMedia( @@ -92,7 +127,11 @@ void main() { }); test('signInViaSocialMedia - переход в состояние loading', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); when( mockAuthRepository.signInViaSocialMedia( @@ -119,7 +158,11 @@ void main() { }); test('signInViaSocialMedia - обработка ошибки', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); when( mockAuthRepository.signInViaSocialMedia( @@ -144,7 +187,11 @@ void main() { }); test('signUpViaSocialMedia - успешная регистрация через соцсеть', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); when( mockAuthRepository.signUpViaSocialMedia( @@ -164,7 +211,9 @@ void main() { ), ).thenAnswer((_) async => tokenFixture.token); - when(mockPrefs.setString(any, any)).thenAnswer((_) async => Future.value()); + when( + mockPrefs.setString(any, any), + ).thenAnswer((_) async => Future.value()); final result = await oauthCubit!.signUpViaSocialMedia( provider: signUpRequestFixture['provider'] as String, @@ -187,48 +236,62 @@ void main() { expect(oauthCubit!.state, isA()); final loadedState = oauthCubit!.state as OAuthStateLoaded; expect(loadedState.token, tokenFixture.token); + expect(await TokenStorage.getTokenFromStorage(), tokenFixture.token); + expect(firebaseApi.registerPushTokenCalls, 1); + expect(firebaseApi.hadAuthToken, isTrue); }); - test('signInViaSocialMedia - обработка ошибки валидации email/phone', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + test( + 'signInViaSocialMedia - обработка ошибки валидации email/phone', + () async { + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); - final dioException = DioException( - requestOptions: RequestOptions(path: '/api/auth/oauth/signin'), - response: Response( - statusCode: 422, - data: { - 'errors': { - 'email': ['Некорректный формат email'], - }, - }, + final dioException = DioException( requestOptions: RequestOptions(path: '/api/auth/oauth/signin'), - ), - ); + response: Response( + statusCode: 422, + data: { + 'errors': { + 'email': ['Некорректный формат email'], + }, + }, + requestOptions: RequestOptions(path: '/api/auth/oauth/signin'), + ), + ); - when( - mockAuthRepository.signInViaSocialMedia( - provider: anyNamed('provider'), - accessToken: anyNamed('accessToken'), - refreshToken: anyNamed('refreshToken'), - clientId: anyNamed('clientId'), - ), - ).thenThrow(dioException); + when( + mockAuthRepository.signInViaSocialMedia( + provider: anyNamed('provider'), + accessToken: anyNamed('accessToken'), + refreshToken: anyNamed('refreshToken'), + clientId: anyNamed('clientId'), + ), + ).thenThrow(dioException); - await oauthCubit!.signInViaSocialMedia( - provider: signInRequestFixture['provider'] as String, - accessToken: signInRequestFixture['access_token'] as String, - refreshToken: signInRequestFixture['refresh_token'] as String, - clientId: signInRequestFixture['client_id'] as String, - ); + await oauthCubit!.signInViaSocialMedia( + provider: signInRequestFixture['provider'] as String, + accessToken: signInRequestFixture['access_token'] as String, + refreshToken: signInRequestFixture['refresh_token'] as String, + clientId: signInRequestFixture['client_id'] as String, + ); - await Future.delayed(const Duration(milliseconds: 100)); - expect(oauthCubit!.state, isA()); - final errorState = oauthCubit!.state as OAuthStateError; - expect(errorState.errorMessage, 'validation_error'); - }); + await Future.delayed(const Duration(milliseconds: 100)); + expect(oauthCubit!.state, isA()); + final errorState = oauthCubit!.state as OAuthStateError; + expect(errorState.errorMessage, 'validation_error'); + }, + ); test('signUpViaSocialMedia - обработка ошибки валидации email', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); final dioException = DioException( requestOptions: RequestOptions(path: '/api/auth/oauth/signup'), @@ -282,82 +345,130 @@ void main() { expect(result[0], RegistrationValidationErrors.incorrectEmailFormat); }); - test('requestOAuthVerification - успешный запрос кода верификации', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + test( + 'requestOAuthVerification - успешный запрос кода верификации', + () async { + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); - const verificationResponse = OAuthVerificationRequestResponse(email: 't***@example.com', status: 'sent'); + const verificationResponse = OAuthVerificationRequestResponse( + email: 't***@example.com', + status: 'sent', + ); - when(mockAuthRepository.requestOAuthVerification(email: anyNamed('email'), phone: anyNamed('phone'))).thenAnswer((_) async => verificationResponse); + when( + mockAuthRepository.requestOAuthVerification( + email: anyNamed('email'), + phone: anyNamed('phone'), + ), + ).thenAnswer((_) async => verificationResponse); - final result = await oauthCubit!.requestOAuthVerification(email: 'test@example.com'); + final result = await oauthCubit!.requestOAuthVerification( + email: 'test@example.com', + ); - expect(result, isNotNull); - expect(result!.email, 't***@example.com'); - expect(oauthCubit!.state, isA()); - }); + expect(result, isNotNull); + expect(result!.email, 't***@example.com'); + expect(oauthCubit!.state, isA()); + }, + ); test('requestOAuthVerification - обработка ошибки', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); - when(mockAuthRepository.requestOAuthVerification(email: anyNamed('email'), phone: anyNamed('phone'))).thenThrow(Exception('Ошибка запроса')); + when( + mockAuthRepository.requestOAuthVerification( + email: anyNamed('email'), + phone: anyNamed('phone'), + ), + ).thenThrow(Exception('Ошибка запроса')); - final result = await oauthCubit!.requestOAuthVerification(email: 'test@example.com'); + final result = await oauthCubit!.requestOAuthVerification( + email: 'test@example.com', + ); expect(result, isNull); expect(oauthCubit!.state, isA()); }); - test('confirmOAuthVerification - успешное подтверждение верификации', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + test( + 'confirmOAuthVerification - успешное подтверждение верификации', + () async { + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); - final verificationResponse = OAuthVerificationConfirmResponse(success: true, message: 'Success', token: tokenFixture.token, user: {}, isNewUser: false); + final verificationResponse = OAuthVerificationConfirmResponse( + success: true, + message: 'Success', + token: tokenFixture.token, + user: {}, + isNewUser: false, + ); - when( - mockAuthRepository.confirmOAuthVerification( - provider: anyNamed('provider'), - accessToken: anyNamed('accessToken'), - refreshToken: anyNamed('refreshToken'), - clientId: anyNamed('clientId'), - id: anyNamed('id'), - email: anyNamed('email'), - phone: anyNamed('phone'), - code: anyNamed('code'), - verificationType: anyNamed('verificationType'), - firstName: anyNamed('firstName'), - lastName: anyNamed('lastName'), - dateOfBirth: anyNamed('dateOfBirth'), - gender: anyNamed('gender'), - weight: anyNamed('weight'), - height: anyNamed('height'), - ), - ).thenAnswer((_) async => verificationResponse); + when( + mockAuthRepository.confirmOAuthVerification( + provider: anyNamed('provider'), + accessToken: anyNamed('accessToken'), + refreshToken: anyNamed('refreshToken'), + clientId: anyNamed('clientId'), + id: anyNamed('id'), + email: anyNamed('email'), + phone: anyNamed('phone'), + code: anyNamed('code'), + verificationType: anyNamed('verificationType'), + firstName: anyNamed('firstName'), + lastName: anyNamed('lastName'), + dateOfBirth: anyNamed('dateOfBirth'), + gender: anyNamed('gender'), + weight: anyNamed('weight'), + height: anyNamed('height'), + ), + ).thenAnswer((_) async => verificationResponse); - await oauthCubit!.confirmOAuthVerification( - provider: 'google', - accessToken: 'access_token', - refreshToken: 'refresh_token', - clientId: 'client_id', - id: 'id', - email: 'test@example.com', - phone: null, - code: '123456', - verificationType: 'email', - firstName: 'Test', - lastName: 'User', - dateOfBirth: '2000-01-01', - gender: 'male', - weight: 70, - height: 175, - ); + await oauthCubit!.confirmOAuthVerification( + provider: 'google', + accessToken: 'access_token', + refreshToken: 'refresh_token', + clientId: 'client_id', + id: 'id', + email: 'test@example.com', + phone: null, + code: '123456', + verificationType: 'email', + firstName: 'Test', + lastName: 'User', + dateOfBirth: '2000-01-01', + gender: 'male', + weight: 70, + height: 175, + ); - await Future.delayed(const Duration(milliseconds: 100)); - expect(oauthCubit!.state, isA()); - final loadedState = oauthCubit!.state as OAuthStateLoaded; - expect(loadedState.token, tokenFixture.token); - }); + await Future.delayed(const Duration(milliseconds: 100)); + expect(oauthCubit!.state, isA()); + final loadedState = oauthCubit!.state as OAuthStateLoaded; + expect(loadedState.token, tokenFixture.token); + expect(await TokenStorage.getTokenFromStorage(), tokenFixture.token); + expect(firebaseApi.registerPushTokenCalls, 1); + expect(firebaseApi.hadAuthToken, isTrue); + }, + ); test('confirmOAuthVerification - обработка ошибки', () async { - oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs); + oauthCubit = OAuthCubit( + authRepository: mockAuthRepository, + firebaseApi: firebaseApi, + prefs: mockPrefs, + ); when( mockAuthRepository.confirmOAuthVerification( diff --git a/test/features/registration_v2/cubit/registration_cubit_v2_test.dart b/test/features/registration_v2/cubit/registration_cubit_v2_test.dart index cec3aa9a..e2d3d8ab 100644 --- a/test/features/registration_v2/cubit/registration_cubit_v2_test.dart +++ b/test/features/registration_v2/cubit/registration_cubit_v2_test.dart @@ -1,4 +1,6 @@ import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/firebase_api.dart'; +import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart'; import 'package:flutter/services.dart'; @@ -9,43 +11,60 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'registration_cubit_v2_test.mocks.dart'; +class _RecordingFirebaseApi extends Fake implements FirebaseApi { + int registerPushTokenCalls = 0; + bool hadAuthToken = false; + + @override + Future registerPushToken() async { + registerPushTokenCalls++; + hadAuthToken = await TokenStorage.haveTokenInStorage(); + } +} + @GenerateMocks([RegistrationRepositoryV2]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); - + group('RegistrationCubitV2', () { late RegistrationCubitV2 cubit; late MockRegistrationRepositoryV2 mockRepository; - const MethodChannel passwordGeneratorChannel = MethodChannel('flutter_password_generator'); + late _RecordingFirebaseApi firebaseApi; + const MethodChannel passwordGeneratorChannel = MethodChannel( + 'flutter_password_generator', + ); setUp(() { mockRepository = MockRegistrationRepositoryV2(); - cubit = RegistrationCubitV2(registrationRepository: mockRepository); - + firebaseApi = _RecordingFirebaseApi(); + cubit = RegistrationCubitV2( + registrationRepository: mockRepository, + firebaseApi: firebaseApi, + ); + // Инициализируем SharedPreferences для тестов SharedPreferences.setMockInitialValues({}); - + // Мокируем MethodChannel для flutter_password_generator - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - passwordGeneratorChannel, - (MethodCall methodCall) async { - if (methodCall.method == 'generateSecureRandomData') { - // Возвращаем Uint8List для генерации пароля - // MethodChannel автоматически преобразует Uint8List в List - return Uint8List.fromList(List.generate(32, (i) => i % 256)); - } - return null; - }, - ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(passwordGeneratorChannel, ( + MethodCall methodCall, + ) async { + if (methodCall.method == 'generateSecureRandomData') { + // Возвращаем Uint8List для генерации пароля + // MethodChannel автоматически преобразует Uint8List в List + return Uint8List.fromList(List.generate(32, (i) => i % 256)); + } + return null; + }); }); tearDown(() { + TokenStorage.clearCache(); cubit.close(); // Очищаем моки - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - passwordGeneratorChannel, - null, - ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(passwordGeneratorChannel, null); }); test('начальное состояние', () { @@ -182,14 +201,24 @@ void main() { cubit.setBirthday(DateTime(1990, 1, 1)); const token = 'test-token'; - when(mockRepository.register(registrationModel: anyNamed('registrationModel'))) - .thenAnswer((_) async => token); + when( + mockRepository.register( + registrationModel: anyNamed('registrationModel'), + ), + ).thenAnswer((_) async => token); final result = await cubit.register(); expect(result.$1, isTrue); expect(result.$2, isFalse); - verify(mockRepository.register(registrationModel: anyNamed('registrationModel'))).called(1); + expect(await TokenStorage.getTokenFromStorage(), token); + expect(firebaseApi.registerPushTokenCalls, 1); + expect(firebaseApi.hadAuthToken, isTrue); + verify( + mockRepository.register( + registrationModel: anyNamed('registrationModel'), + ), + ).called(1); }); test('register - возвращает false при невалидных полях', () async { @@ -199,7 +228,11 @@ void main() { expect(result.$1, isFalse); expect(result.$2, isTrue); - verifyNever(mockRepository.register(registrationModel: anyNamed('registrationModel'))); + verifyNever( + mockRepository.register( + registrationModel: anyNamed('registrationModel'), + ), + ); }); test('register - обработка ошибки валидации email', () async { @@ -226,16 +259,21 @@ void main() { ), ); - when(mockRepository.register(registrationModel: anyNamed('registrationModel'))) - .thenThrow(dioException); + when( + mockRepository.register( + registrationModel: anyNamed('registrationModel'), + ), + ).thenThrow(dioException); final result = await cubit.register(); expect(result.$1, isFalse); expect(result.$2, isTrue); expect(cubit.state.isEmailValid, isFalse); - expect(cubit.state.emailDisplayError, equals('Некорректный формат email')); + expect( + cubit.state.emailDisplayError, + equals('Некорректный формат email'), + ); }); }); } -