feat(test): comments, auth, feed

This commit is contained in:
mso7nueh
2025-11-28 14:13:05 +03:00
parent 7df32dabd2
commit 2a25be5aa4
12 changed files with 1774 additions and 1 deletions

3
.gitignore vendored
View File

@@ -19,4 +19,5 @@ ios/XCBuildData/
env/.env
.env
android/key.properties
app/upload-keystore.jks
app/upload-keystore.jks
coverage/

View File

@@ -0,0 +1,419 @@
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/core/comments/comments_cubit/comments_state.dart';
import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
import 'package:eatvio_app/core/models/pagination_model.dart';
import 'package:eatvio_app/features/feed/models/comment_model.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'comments_cubit_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<FeedRepository>(),
MockSpec<CommentsRepository>(),
])
void main() {
group('CommentsCubit', () {
CommentsCubit? commentsCubit;
late MockFeedRepository mockFeedRepository;
late MockCommentsRepository mockCommentsRepository;
late CommentModel testCommentModel;
setUp(() {
mockFeedRepository = MockFeedRepository();
mockCommentsRepository = MockCommentsRepository();
testCommentModel = CommentModel(
data: [],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
});
tearDown(() async {
if (commentsCubit != null && !commentsCubit!.isClosed) {
await Future.delayed(const Duration(milliseconds: 100));
commentsCubit!.close();
commentsCubit = null;
}
});
test('начальное состояние - CommentsState', () {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
expect(commentsCubit!.state, const CommentsState());
});
test('getComments - успешная загрузка комментариев для blogPost', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
when(mockFeedRepository.getBlogPostComments(id: postId))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.getComments(postId, CommentSourceType.blogPost, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment, testCommentModel);
verify(mockFeedRepository.getBlogPostComments(id: postId)).called(1);
});
test('getComments - успешная загрузка комментариев для diet', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const dietId = '1';
when(mockFeedRepository.getDietsComments(id: dietId))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.getComments(dietId, CommentSourceType.diet, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
verify(mockFeedRepository.getDietsComments(id: dietId)).called(1);
});
test('getComments - успешная загрузка комментариев для recipe', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const recipeId = 'recipe-slug';
when(mockFeedRepository.getRecipesComments(id: recipeId))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.getComments(recipeId, CommentSourceType.recipe, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
verify(mockFeedRepository.getRecipesComments(id: recipeId)).called(1);
});
test('getComments - успешная загрузка комментариев для plan', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const planSlug = 'plan-slug';
when(mockFeedRepository.getPlansComments(slug: planSlug))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.getComments(planSlug, CommentSourceType.plan, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
verify(mockFeedRepository.getPlansComments(slug: planSlug)).called(1);
});
test('getComments - переход в состояние loading при первом запросе', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
when(mockFeedRepository.getBlogPostComments(id: postId))
.thenAnswer((_) async {
await Future.delayed(const Duration(milliseconds: 50));
return testCommentModel;
});
final future = commentsCubit!.getComments(postId, CommentSourceType.blogPost, true);
await Future.delayed(const Duration(milliseconds: 10));
expect(commentsCubit!.state, const CommentsState.loading());
await future;
});
test('getComments - обработка ошибки', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
when(mockFeedRepository.getBlogPostComments(id: postId))
.thenThrow(Exception('Ошибка загрузки'));
await commentsCubit!.getComments(postId, CommentSourceType.blogPost, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateError>());
final errorState = commentsCubit!.state as CommentsStateError;
expect(errorState.errorMessage, contains('Exception'));
});
test('sendComment - успешная отправка комментария для blogPost', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
const commentText = 'Тестовый комментарий';
// Устанавливаем начальное состояние
commentsCubit!.emit(CommentsState.loaded(comment: testCommentModel));
when(mockFeedRepository.sendBlogPostComment(
id: postId,
commentText: commentText,
parentCommentId: null,
)).thenAnswer((_) async => Future.value());
when(mockFeedRepository.getBlogPostComments(id: postId))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.sendComment(
id: postId,
commentText: commentText,
commentSourceType: CommentSourceType.blogPost,
);
verify(mockFeedRepository.sendBlogPostComment(
id: postId,
commentText: commentText,
parentCommentId: null,
)).called(1);
verify(mockFeedRepository.getBlogPostComments(id: postId)).called(1);
});
test('sendComment - обработка ошибки при отправке', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
const commentText = 'Тестовый комментарий';
commentsCubit!.emit(CommentsState.loaded(comment: testCommentModel));
when(mockFeedRepository.sendBlogPostComment(
id: postId,
commentText: commentText,
parentCommentId: null,
)).thenThrow(Exception('Ошибка отправки'));
await commentsCubit!.sendComment(
id: postId,
commentText: commentText,
commentSourceType: CommentSourceType.blogPost,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateError>());
});
test('deleteComment - успешное удаление комментария', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
final commentWithData = testCommentModel.copyWith(
data: [
Comment(
id: commentId,
body: 'Комментарий для удаления',
authorName: 'Автор',
authorSlug: 'author-slug',
authorProfileIcon: 'https://example.com/photo.jpg',
createdAt: DateTime.now(),
childrenComments: [],
),
],
);
commentsCubit!.emit(CommentsState.loaded(comment: commentWithData));
when(mockCommentsRepository.deleteComment(id: commentId))
.thenAnswer((_) async => Future.value());
final result = await commentsCubit!.deleteComment(id: commentId);
expect(result, true);
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment.data, isEmpty);
verify(mockCommentsRepository.deleteComment(id: commentId)).called(1);
});
test('deleteComment - возвращает false если состояние не loaded', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
commentsCubit!.emit(const CommentsState());
final result = await commentsCubit!.deleteComment(id: commentId);
expect(result, false);
verifyNever(mockCommentsRepository.deleteComment(id: commentId));
});
test('deleteComment - обработка ошибки при удалении', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
final commentWithData = testCommentModel.copyWith(
data: [
Comment(
id: commentId,
body: 'Комментарий',
authorName: 'Автор',
authorSlug: 'author-slug',
authorProfileIcon: 'https://example.com/photo.jpg',
createdAt: DateTime.now(),
childrenComments: [],
),
],
);
commentsCubit!.emit(CommentsState.loaded(comment: commentWithData));
when(mockCommentsRepository.deleteComment(id: commentId))
.thenThrow(Exception('Ошибка удаления'));
final result = await commentsCubit!.deleteComment(id: commentId);
expect(result, false);
// Состояние должно вернуться к исходному
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment.data.length, 1);
});
test('updateComment - успешное обновление комментария', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
const newBody = 'Обновлённый комментарий';
final commentWithData = testCommentModel.copyWith(
data: [
Comment(
id: commentId,
body: 'Старый комментарий',
authorName: 'Автор',
authorSlug: 'author-slug',
authorProfileIcon: 'https://example.com/photo.jpg',
createdAt: DateTime.now(),
childrenComments: [],
),
],
);
commentsCubit!.emit(CommentsState.loaded(comment: commentWithData));
when(mockCommentsRepository.updateComment(id: commentId, body: newBody))
.thenAnswer((_) async => Future.value());
final result = await commentsCubit!.updateComment(id: commentId, body: newBody);
expect(result, true);
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment.data.first.body, newBody);
verify(mockCommentsRepository.updateComment(id: commentId, body: newBody)).called(1);
});
test('updateComment - обработка ошибки при обновлении', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
const newBody = 'Обновлённый комментарий';
const oldBody = 'Старый комментарий';
final commentWithData = testCommentModel.copyWith(
data: [
Comment(
id: commentId,
body: oldBody,
authorName: 'Автор',
authorSlug: 'author-slug',
authorProfileIcon: 'https://example.com/photo.jpg',
createdAt: DateTime.now(),
childrenComments: [],
),
],
);
commentsCubit!.emit(CommentsState.loaded(comment: commentWithData));
when(mockCommentsRepository.updateComment(id: commentId, body: newBody))
.thenThrow(Exception('Ошибка обновления'));
final result = await commentsCubit!.updateComment(id: commentId, body: newBody);
expect(result, false);
// Состояние должно вернуться к исходному
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment.data.first.body, oldBody);
});
test('setReplyId - устанавливает ID ответа', () {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const replyId = 5;
commentsCubit!.emit(CommentsState.loaded(comment: testCommentModel));
commentsCubit!.setReplyId(replyId);
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.replyId, replyId);
});
test('setReplyId - сбрасывает ID ответа', () {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
commentsCubit!.emit(CommentsState.loaded(comment: testCommentModel, replyId: 5));
commentsCubit!.setReplyId(null);
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.replyId, isNull);
});
});
}

View File

@@ -0,0 +1,80 @@
import 'package:eatvio_app/core/comments/models/comment_update_model.dart';
import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
import 'package:eatvio_app/core/comments/services/comments_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'comments_repository_test.mocks.dart';
@GenerateNiceMocks([MockSpec<CommentsService>()])
void main() {
group('CommentsRepository', () {
late CommentsRepository commentsRepository;
late MockCommentsService mockCommentsService;
setUp(() {
mockCommentsService = MockCommentsService();
commentsRepository = CommentsRepository(service: mockCommentsService);
});
test('deleteComment - успешное удаление комментария', () async {
const commentId = 1;
when(mockCommentsService.deleteComment(commentId))
.thenAnswer((_) async => Future.value());
await commentsRepository.deleteComment(id: commentId);
verify(mockCommentsService.deleteComment(commentId)).called(1);
});
test('deleteComment - пробрасывает исключение', () async {
const commentId = 1;
when(mockCommentsService.deleteComment(commentId))
.thenThrow(Exception('Ошибка удаления'));
expect(
() => commentsRepository.deleteComment(id: commentId),
throwsException,
);
});
test('updateComment - успешное обновление комментария', () async {
const commentId = 1;
const newBody = 'Обновлённый текст комментария';
final updateModel = CommentUpdateModel(body: newBody);
when(mockCommentsService.updateComment(commentId, updateModel))
.thenAnswer((_) async => Future.value());
await commentsRepository.updateComment(id: commentId, body: newBody);
verify(mockCommentsService.updateComment(
commentId,
argThat(
predicate<CommentUpdateModel>(
(m) => m.body == newBody,
),
),
)).called(1);
});
test('updateComment - пробрасывает исключение', () async {
const commentId = 1;
const newBody = 'Обновлённый текст';
when(mockCommentsService.updateComment(
commentId,
any,
)).thenThrow(Exception('Ошибка обновления'));
expect(
() => commentsRepository.updateComment(id: commentId, body: newBody),
throwsException,
);
});
});
}

View File

@@ -0,0 +1,190 @@
import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/auth/models/token_model.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:flutter/services.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 '../../../../../fixtures/doubles/auth_doubles.dart';
import '../../../../../fixtures/fixture_reader.dart';
import 'oauth_cubit_test.mocks.dart';
@GenerateNiceMocks([MockSpec<AuthRepository>(), MockSpec<Prefs>()])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('OAuthCubit', () {
OAuthCubit? oauthCubit;
late MockAuthRepository mockAuthRepository;
late MockPrefs mockPrefs;
late TokenModel tokenFixture;
late Map<String, dynamic> signInRequestFixture;
late Map<String, dynamic> 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');
mockAuthRepository = MockAuthRepository();
mockPrefs = MockPrefs();
SharedPreferences.setMockInitialValues({});
// Mock Fluttertoast plugin
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('PonnamKarthik/fluttertoast'), (
MethodCall methodCall,
) async {
return null;
});
});
tearDown(() async {
if (oauthCubit != null && !oauthCubit!.isClosed) {
await Future.delayed(const Duration(milliseconds: 100));
oauthCubit!.close();
oauthCubit = null;
}
// Clear mock handlers
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('PonnamKarthik/fluttertoast'), null);
});
test('начальное состояние - OAuthState', () {
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
expect(oauthCubit!.state, const OAuthState());
});
test('signInViaSocialMedia - успешный вход через соцсеть', () async {
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
when(
mockAuthRepository.signInViaSocialMedia(
provider: signInRequestFixture['provider'] as String,
accessToken: signInRequestFixture['accessToken'] as String,
refreshToken: signInRequestFixture['refreshToken'] as String,
clientId: signInRequestFixture['clientId'] as String,
),
).thenAnswer((_) async => tokenFixture);
await oauthCubit!.signInViaSocialMedia(
provider: signInRequestFixture['provider'] as String,
accessToken: signInRequestFixture['accessToken'] as String,
refreshToken: signInRequestFixture['refreshToken'] as String,
clientId: signInRequestFixture['clientId'] as String,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(oauthCubit!.state, isA<OAuthStateLoaded>());
final loadedState = oauthCubit!.state as OAuthStateLoaded;
expect(loadedState.token, tokenFixture.token);
// Проверяем, что токен сохранён
final savedToken = await TokenStorage.getTokenFromStorage();
expect(savedToken, tokenFixture.token);
});
test('signInViaSocialMedia - переход в состояние loading', () async {
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
when(
mockAuthRepository.signInViaSocialMedia(
provider: anyNamed('provider'),
accessToken: anyNamed('accessToken'),
refreshToken: anyNamed('refreshToken'),
clientId: anyNamed('clientId'),
),
).thenAnswer((_) async {
await Future.delayed(const Duration(milliseconds: 50));
return tokenFixture;
});
final future = oauthCubit!.signInViaSocialMedia(
provider: signInRequestFixture['provider'] as String,
accessToken: signInRequestFixture['accessToken'] as String,
refreshToken: signInRequestFixture['refreshToken'] as String,
clientId: signInRequestFixture['clientId'] as String,
);
await Future.delayed(const Duration(milliseconds: 10));
expect(oauthCubit!.state, isA<OAuthStateLoading>());
await future;
});
test('signInViaSocialMedia - обработка ошибки', () async {
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
when(
mockAuthRepository.signInViaSocialMedia(
provider: anyNamed('provider'),
accessToken: anyNamed('accessToken'),
refreshToken: anyNamed('refreshToken'),
clientId: anyNamed('clientId'),
),
).thenThrow(Exception('Ошибка авторизации'));
await oauthCubit!.signInViaSocialMedia(
provider: signInRequestFixture['provider'] as String,
accessToken: signInRequestFixture['accessToken'] as String,
refreshToken: signInRequestFixture['refreshToken'] as String,
clientId: signInRequestFixture['clientId'] as String,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(oauthCubit!.state, isA<OAuthStateError>());
final errorState = oauthCubit!.state as OAuthStateError;
expect(errorState.errorMessage, contains('Exception'));
});
test('signUpViaSocialMedia - успешная регистрация через соцсеть', () async {
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
when(
mockAuthRepository.signUpViaSocialMedia(
provider: anyNamed('provider'),
accessToken: anyNamed('accessToken'),
id: anyNamed('id'),
refreshToken: anyNamed('refreshToken'),
firstName: anyNamed('firstName'),
lastName: anyNamed('lastName'),
email: anyNamed('email'),
phone: anyNamed('phone'),
dateOfBirth: anyNamed('dateOfBirth'),
gender: anyNamed('gender'),
weight: anyNamed('weight'),
height: anyNamed('height'),
clientId: anyNamed('clientId'),
),
).thenAnswer((_) async => tokenFixture.token);
when(mockPrefs.setString(any, any)).thenAnswer((_) async => Future.value());
final result = await oauthCubit!.signUpViaSocialMedia(
provider: signUpRequestFixture['provider'] as String,
accessToken: signUpRequestFixture['accessToken'] as String,
id: signUpRequestFixture['id'] as String,
refreshToken: signUpRequestFixture['refreshToken'] as String,
firstName: signUpRequestFixture['firstName'] as String,
lastName: signUpRequestFixture['lastName'] as String,
email: signUpRequestFixture['email'] as String,
phone: signUpRequestFixture['phone'] as String,
dateOfBirth: signUpRequestFixture['dateOfBirth'] as String,
gender: signUpRequestFixture['gender'] as String,
weight: signUpRequestFixture['weight'] as int,
height: signUpRequestFixture['height'] as int,
clientId: signUpRequestFixture['clientId'] as String,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(result, isNull); // Нет ошибок валидации
expect(oauthCubit!.state, isA<OAuthStateLoaded>());
final loadedState = oauthCubit!.state as OAuthStateLoaded;
expect(loadedState.token, tokenFixture.token);
});
});
}

View File

@@ -0,0 +1,216 @@
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart';
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import '../../../../../fixtures/fixture_reader.dart';
import 'reset_password_cubit_test.mocks.dart';
@GenerateNiceMocks([MockSpec<ResetPasswordRepository>()])
void main() {
group('ResetPasswordCubit', () {
ResetPasswordCubit? resetPasswordCubit;
late MockResetPasswordRepository mockResetPasswordRepository;
late Map<String, dynamic> resetPasswordFixture;
setUp(() {
resetPasswordFixture = FixtureReader.readAsMap('auth/reset_password_request.json');
mockResetPasswordRepository = MockResetPasswordRepository();
});
tearDown(() async {
if (resetPasswordCubit != null && !resetPasswordCubit!.isClosed) {
await Future.delayed(const Duration(milliseconds: 100));
resetPasswordCubit!.close();
resetPasswordCubit = null;
}
});
test('начальное состояние - ResetPasswordState.empty', () {
resetPasswordCubit = ResetPasswordCubit(
resetPasswordRepository: mockResetPasswordRepository,
);
expect(resetPasswordCubit!.state, const ResetPasswordState.empty());
});
test('trySendEmailCode - успешная отправка кода', () async {
resetPasswordCubit = ResetPasswordCubit(
resetPasswordRepository: mockResetPasswordRepository,
);
when(mockResetPasswordRepository.sendEmailCodeForPasswordChange(
resetPasswordFixture['email'] as String,
)).thenAnswer((_) async => true);
await resetPasswordCubit!.trySendEmailCode(
resetPasswordFixture['email'] as String,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(resetPasswordCubit!.state, const ResetPasswordState.codeSendSuccess());
verify(mockResetPasswordRepository.sendEmailCodeForPasswordChange(
resetPasswordFixture['email'] as String,
)).called(1);
});
test('trySendEmailCode - переход в состояние loading', () async {
resetPasswordCubit = ResetPasswordCubit(
resetPasswordRepository: mockResetPasswordRepository,
);
when(mockResetPasswordRepository.sendEmailCodeForPasswordChange(
any,
)).thenAnswer((_) async {
await Future.delayed(const Duration(milliseconds: 50));
return true;
});
final future = resetPasswordCubit!.trySendEmailCode(
resetPasswordFixture['email'] as String,
);
await Future.delayed(const Duration(milliseconds: 10));
expect(resetPasswordCubit!.state, const ResetPasswordState.loadingSendEmailCode());
await future;
});
test('trySendEmailCode - ошибка отправки кода', () async {
resetPasswordCubit = ResetPasswordCubit(
resetPasswordRepository: mockResetPasswordRepository,
);
when(mockResetPasswordRepository.sendEmailCodeForPasswordChange(
resetPasswordFixture['email'] as String,
)).thenAnswer((_) async => false);
await resetPasswordCubit!.trySendEmailCode(
resetPasswordFixture['email'] as String,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(resetPasswordCubit!.state, const ResetPasswordState.codeSendError());
});
test('trySendEmailCode - обработка исключения', () async {
resetPasswordCubit = ResetPasswordCubit(
resetPasswordRepository: mockResetPasswordRepository,
);
when(mockResetPasswordRepository.sendEmailCodeForPasswordChange(
resetPasswordFixture['email'] as String,
)).thenThrow(Exception('Ошибка сети'));
await resetPasswordCubit!.trySendEmailCode(
resetPasswordFixture['email'] as String,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(resetPasswordCubit!.state, const ResetPasswordState.codeSendError());
});
test('tryResetPassword - успешный сброс пароля', () async {
resetPasswordCubit = ResetPasswordCubit(
resetPasswordRepository: mockResetPasswordRepository,
);
when(mockResetPasswordRepository.confirmPasswordChange(
resetPasswordFixture['code'] as String,
resetPasswordFixture['password'] as String,
resetPasswordFixture['passwordConfirmation'] as String,
resetPasswordFixture['email'] as String,
)).thenAnswer((_) async => true);
await resetPasswordCubit!.tryResetPassword(
resetPasswordFixture['email'] as String,
resetPasswordFixture['code'] as String,
resetPasswordFixture['password'] as String,
resetPasswordFixture['passwordConfirmation'] as String,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(resetPasswordCubit!.state, const ResetPasswordState.resetPasswordSuccess());
verify(mockResetPasswordRepository.confirmPasswordChange(
resetPasswordFixture['code'] as String,
resetPasswordFixture['password'] as String,
resetPasswordFixture['passwordConfirmation'] as String,
resetPasswordFixture['email'] as String,
)).called(1);
});
test('tryResetPassword - переход в состояние loading', () async {
resetPasswordCubit = ResetPasswordCubit(
resetPasswordRepository: mockResetPasswordRepository,
);
when(mockResetPasswordRepository.confirmPasswordChange(
any,
any,
any,
any,
)).thenAnswer((_) async {
await Future.delayed(const Duration(milliseconds: 50));
return true;
});
final future = resetPasswordCubit!.tryResetPassword(
resetPasswordFixture['email'] as String,
resetPasswordFixture['code'] as String,
resetPasswordFixture['password'] as String,
resetPasswordFixture['passwordConfirmation'] as String,
);
await Future.delayed(const Duration(milliseconds: 10));
expect(resetPasswordCubit!.state, const ResetPasswordState.loadingResetPassword());
await future;
});
test('tryResetPassword - ошибка сброса пароля', () async {
resetPasswordCubit = ResetPasswordCubit(
resetPasswordRepository: mockResetPasswordRepository,
);
when(mockResetPasswordRepository.confirmPasswordChange(
resetPasswordFixture['code'] as String,
resetPasswordFixture['password'] as String,
resetPasswordFixture['passwordConfirmation'] as String,
resetPasswordFixture['email'] as String,
)).thenAnswer((_) async => false);
await resetPasswordCubit!.tryResetPassword(
resetPasswordFixture['email'] as String,
resetPasswordFixture['code'] as String,
resetPasswordFixture['password'] as String,
resetPasswordFixture['passwordConfirmation'] as String,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(resetPasswordCubit!.state, const ResetPasswordState.resetPasswordError());
});
test('tryResetPassword - обработка исключения', () async {
resetPasswordCubit = ResetPasswordCubit(
resetPasswordRepository: mockResetPasswordRepository,
);
when(mockResetPasswordRepository.confirmPasswordChange(
any,
any,
any,
any,
)).thenThrow(Exception('Неверный код'));
await resetPasswordCubit!.tryResetPassword(
resetPasswordFixture['email'] as String,
resetPasswordFixture['code'] as String,
resetPasswordFixture['password'] as String,
resetPasswordFixture['passwordConfirmation'] as String,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(resetPasswordCubit!.state, const ResetPasswordState.resetPasswordError());
});
});
}

View File

@@ -0,0 +1,237 @@
import 'package:eatvio_app/features/auth/models/credentials_model.dart';
import 'package:eatvio_app/features/auth/models/oauth_login_request.dart';
import 'package:eatvio_app/features/auth/models/oauth_model.dart';
import 'package:eatvio_app/features/auth/models/token_model.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:eatvio_app/features/auth/services/auth_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import '../../../fixtures/fixture_reader.dart';
import 'auth_repository_test.mocks.dart';
@GenerateNiceMocks([MockSpec<AuthService>()])
void main() {
group('AuthRepository', () {
late AuthRepository authRepository;
late MockAuthService mockAuthService;
late TokenModel tokenFixture;
late Map<String, dynamic> credentialsFixture;
late Map<String, dynamic> oauthSignInFixture;
late Map<String, dynamic> oauthSignUpFixture;
setUp(() {
tokenFixture = TokenModel.fromJson(FixtureReader.readAsMap('auth/token_response.json'));
credentialsFixture = FixtureReader.readAsMap('auth/login_credentials.json');
oauthSignInFixture = FixtureReader.readAsMap('auth/oauth_signin_request.json');
oauthSignUpFixture = FixtureReader.readAsMap('auth/oauth_signup_request.json');
mockAuthService = MockAuthService();
authRepository = AuthRepository(authService: mockAuthService);
});
test('getTokenByCredentials - успешное получение токена', () async {
final credentials = CredentialsModel(
email: credentialsFixture['email'] as String,
password: credentialsFixture['password'] as String,
);
when(mockAuthService.getTokenByCredentials(credentials: credentials))
.thenAnswer((_) async => tokenFixture);
final result = await authRepository.getTokenByCredentials(
credentialsFixture['email'] as String,
credentialsFixture['password'] as String,
);
expect(result, isA<TokenModel>());
expect(result.token, tokenFixture.token);
verify(mockAuthService.getTokenByCredentials(credentials: credentials)).called(1);
});
test('getTokenByCredentials - пробрасывает исключение', () async {
final credentials = CredentialsModel(
email: credentialsFixture['email'] as String,
password: credentialsFixture['password'] as String,
);
when(mockAuthService.getTokenByCredentials(credentials: credentials))
.thenThrow(Exception('Ошибка авторизации'));
expect(
() => authRepository.getTokenByCredentials(
credentialsFixture['email'] as String,
credentialsFixture['password'] as String,
),
throwsException,
);
});
test('signInViaSocialMedia - успешный вход через соцсеть', () async {
final oauthRequest = OAuthLoginRequest(
provider: oauthSignInFixture['provider'] as String,
accessToken: oauthSignInFixture['accessToken'] as String,
refreshToken: oauthSignInFixture['refreshToken'] as String,
clientId: oauthSignInFixture['clientId'] as String,
);
when(mockAuthService.signInViaSocialMedia(body: oauthRequest))
.thenAnswer((_) async => tokenFixture);
final result = await authRepository.signInViaSocialMedia(
provider: oauthSignInFixture['provider'] as String,
accessToken: oauthSignInFixture['accessToken'] as String,
refreshToken: oauthSignInFixture['refreshToken'] as String,
clientId: oauthSignInFixture['clientId'] as String,
);
expect(result, isA<TokenModel>());
expect(result.token, tokenFixture.token);
verify(mockAuthService.signInViaSocialMedia(body: oauthRequest)).called(1);
});
test('signInViaSocialMedia - пробрасывает исключение', () async {
final oauthRequest = OAuthLoginRequest(
provider: oauthSignInFixture['provider'] as String,
accessToken: oauthSignInFixture['accessToken'] as String,
refreshToken: oauthSignInFixture['refreshToken'] as String,
clientId: oauthSignInFixture['clientId'] as String,
);
when(mockAuthService.signInViaSocialMedia(body: oauthRequest))
.thenThrow(Exception('Ошибка OAuth'));
expect(
() => authRepository.signInViaSocialMedia(
provider: oauthSignInFixture['provider'] as String,
accessToken: oauthSignInFixture['accessToken'] as String,
refreshToken: oauthSignInFixture['refreshToken'] as String,
clientId: oauthSignInFixture['clientId'] as String,
),
throwsException,
);
});
test('signUpViaSocialMedia - успешная регистрация через соцсеть', () async {
final oauthModel = OAuthModel(
provider: oauthSignUpFixture['provider'] as String,
accessToken: oauthSignUpFixture['accessToken'] as String,
id: oauthSignUpFixture['id'] as String,
refreshToken: oauthSignUpFixture['refreshToken'] as String,
firstName: oauthSignUpFixture['firstName'] as String,
lastName: oauthSignUpFixture['lastName'] as String,
email: oauthSignUpFixture['email'] as String,
phone: oauthSignUpFixture['phone'] as String,
dateOfBirth: oauthSignUpFixture['dateOfBirth'] as String,
gender: 'male', // Преобразуется из 'М'
weight: oauthSignUpFixture['weight'] as int,
height: oauthSignUpFixture['height'] as int,
clientId: oauthSignUpFixture['clientId'] as String,
);
when(mockAuthService.signUpViaSocialMedia(body: oauthModel))
.thenAnswer((_) async => <String, Object?>{'token': tokenFixture.token});
final result = await authRepository.signUpViaSocialMedia(
provider: oauthSignUpFixture['provider'] as String,
accessToken: oauthSignUpFixture['accessToken'] as String,
id: oauthSignUpFixture['id'] as String,
refreshToken: oauthSignUpFixture['refreshToken'] as String,
firstName: oauthSignUpFixture['firstName'] as String,
lastName: oauthSignUpFixture['lastName'] as String,
email: oauthSignUpFixture['email'] as String,
phone: oauthSignUpFixture['phone'] as String,
dateOfBirth: oauthSignUpFixture['dateOfBirth'] as String,
gender: oauthSignUpFixture['gender'] as String,
weight: oauthSignUpFixture['weight'] as int,
height: oauthSignUpFixture['height'] as int,
clientId: oauthSignUpFixture['clientId'] as String,
);
expect(result, tokenFixture.token);
verify(mockAuthService.signUpViaSocialMedia(body: oauthModel)).called(1);
});
test('signUpViaSocialMedia - преобразует женский пол', () async {
final oauthModel = OAuthModel(
provider: oauthSignUpFixture['provider'] as String,
accessToken: oauthSignUpFixture['accessToken'] as String,
id: oauthSignUpFixture['id'] as String,
refreshToken: oauthSignUpFixture['refreshToken'] as String,
firstName: oauthSignUpFixture['firstName'] as String,
lastName: oauthSignUpFixture['lastName'] as String,
email: oauthSignUpFixture['email'] as String,
phone: oauthSignUpFixture['phone'] as String,
dateOfBirth: oauthSignUpFixture['dateOfBirth'] as String,
gender: 'female', // Преобразуется из 'Ж'
weight: oauthSignUpFixture['weight'] as int,
height: oauthSignUpFixture['height'] as int,
clientId: oauthSignUpFixture['clientId'] as String,
);
when(mockAuthService.signUpViaSocialMedia(body: oauthModel))
.thenAnswer((_) async => <String, Object?>{'token': tokenFixture.token});
final result = await authRepository.signUpViaSocialMedia(
provider: oauthSignUpFixture['provider'] as String,
accessToken: oauthSignUpFixture['accessToken'] as String,
id: oauthSignUpFixture['id'] as String,
refreshToken: oauthSignUpFixture['refreshToken'] as String,
firstName: oauthSignUpFixture['firstName'] as String,
lastName: oauthSignUpFixture['lastName'] as String,
email: oauthSignUpFixture['email'] as String,
phone: oauthSignUpFixture['phone'] as String,
dateOfBirth: oauthSignUpFixture['dateOfBirth'] as String,
gender: 'Ж',
weight: oauthSignUpFixture['weight'] as int,
height: oauthSignUpFixture['height'] as int,
clientId: oauthSignUpFixture['clientId'] as String,
);
expect(result, tokenFixture.token);
verify(mockAuthService.signUpViaSocialMedia(body: oauthModel)).called(1);
});
test('signUpViaSocialMedia - пробрасывает исключение', () async {
final oauthModel = OAuthModel(
provider: oauthSignUpFixture['provider'] as String,
accessToken: oauthSignUpFixture['accessToken'] as String,
id: oauthSignUpFixture['id'] as String,
refreshToken: oauthSignUpFixture['refreshToken'] as String,
firstName: oauthSignUpFixture['firstName'] as String,
lastName: oauthSignUpFixture['lastName'] as String,
email: oauthSignUpFixture['email'] as String,
phone: oauthSignUpFixture['phone'] as String,
dateOfBirth: oauthSignUpFixture['dateOfBirth'] as String,
gender: 'male', // Преобразуется из 'М'
weight: oauthSignUpFixture['weight'] as int,
height: oauthSignUpFixture['height'] as int,
clientId: oauthSignUpFixture['clientId'] as String,
);
when(mockAuthService.signUpViaSocialMedia(body: oauthModel))
.thenThrow(Exception('Ошибка регистрации'));
expect(
() => authRepository.signUpViaSocialMedia(
provider: oauthSignUpFixture['provider'] as String,
accessToken: oauthSignUpFixture['accessToken'] as String,
id: oauthSignUpFixture['id'] as String,
refreshToken: oauthSignUpFixture['refreshToken'] as String,
firstName: oauthSignUpFixture['firstName'] as String,
lastName: oauthSignUpFixture['lastName'] as String,
email: oauthSignUpFixture['email'] as String,
phone: oauthSignUpFixture['phone'] as String,
dateOfBirth: oauthSignUpFixture['dateOfBirth'] as String,
gender: oauthSignUpFixture['gender'] as String,
weight: oauthSignUpFixture['weight'] as int,
height: oauthSignUpFixture['height'] as int,
clientId: oauthSignUpFixture['clientId'] as String,
),
throwsException,
);
});
});
}

View File

@@ -0,0 +1,223 @@
import 'package:eatvio_app/core/models/pagination_model.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'feed_cubit_test.mocks.dart';
@GenerateNiceMocks([MockSpec<FeedRepository>()])
void main() {
group('FeedCubit', () {
FeedCubit? feedCubit;
late MockFeedRepository mockFeedRepository;
late FeedBlogPostSettingsModel feedBlogPostsFixture;
setUp(() {
mockFeedRepository = MockFeedRepository();
// Создаём тестовые данные
final testPosts = [
FeedBlogPostModel(
id: 1,
state: 'published',
image: 'https://example.com/image1.jpg',
description: 'Описание поста 1',
author: const Author(
authorSlug: 'author-1',
authorName: 'Автор 1',
authorProfileIcon: 'https://example.com/photo1.jpg',
authorIsMe: false,
isEntitySubscribedByMe: null,
authorPlaceInRating: null,
),
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
title: 'Тестовый пост 1',
totalLikes: 10,
totalViews: 100,
totalComments: 5,
wasLikedByMe: false,
),
FeedBlogPostModel(
id: 2,
state: 'published',
image: 'https://example.com/image2.jpg',
description: 'Описание поста 2',
author: const Author(
authorSlug: 'author-2',
authorName: 'Автор 2',
authorProfileIcon: 'https://example.com/photo2.jpg',
authorIsMe: false,
isEntitySubscribedByMe: null,
authorPlaceInRating: null,
),
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
title: 'Тестовый пост 2',
totalLikes: 20,
totalViews: 200,
totalComments: 10,
wasLikedByMe: true,
),
];
feedBlogPostsFixture = FeedBlogPostSettingsModel(
feedBlogPostList: testPosts,
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 3,
links: [],
path: null,
perPage: 10,
to: null,
total: 25,
totalMinPrice: null,
totalMaxPrice: null,
),
);
});
tearDown(() async {
if (feedCubit != null && !feedCubit!.isClosed) {
await Future.delayed(const Duration(milliseconds: 100));
feedCubit!.close();
feedCubit = null;
}
});
test('начальное состояние - FeedStateLoading', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
expect(feedCubit!.state, const FeedState.loading());
});
test('loadFeedBlogPostList - успешная загрузка постов', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
when(mockFeedRepository.getFeedBlogPosts(query: null))
.thenAnswer((_) async => feedBlogPostsFixture);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
expect(loadedState.feedBlogPostList.length, 2);
expect(loadedState.isLoading, false);
expect(feedCubit!.lastPage, 3);
});
test('loadFeedBlogPostList - загрузка с запросом', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const query = 'тест';
when(mockFeedRepository.getFeedBlogPosts(query: query))
.thenAnswer((_) async => feedBlogPostsFixture);
await feedCubit!.loadFeedBlogPostList(query: query);
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateLoaded>());
verify(mockFeedRepository.getFeedBlogPosts(query: query)).called(1);
});
test('loadFeedBlogPostList - обработка ошибки', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
when(mockFeedRepository.getFeedBlogPosts(query: anyNamed('query')))
.thenThrow(Exception('Ошибка загрузки'));
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateError>());
final errorState = feedCubit!.state as FeedStateError;
expect(errorState.errorMessage, contains('Exception'));
});
test('updateFeedBlogPostList - обновление лайка поста', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
// Сначала загружаем посты
when(mockFeedRepository.getFeedBlogPosts(query: anyNamed('query')))
.thenAnswer((_) async => feedBlogPostsFixture);
// Устанавливаем состояние loaded вручную для теста
feedCubit!.emit(FeedState.loaded(
feedBlogPostList: feedBlogPostsFixture.feedBlogPostList,
isLoading: false,
));
feedCubit!.lastPage = 3;
final postToUpdate = feedBlogPostsFixture.feedBlogPostList.first;
feedCubit!.updateFeedBlogPostList(postToUpdate, true);
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
final updatedPost = loadedState.feedBlogPostList.firstWhere((p) => p.id == postToUpdate.id);
expect(updatedPost.wasLikedByMe, true);
expect(updatedPost.totalLikes, postToUpdate.totalLikes + 1);
});
test('updateFeedBlogPostList - снятие лайка', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
feedCubit!.emit(FeedState.loaded(
feedBlogPostList: feedBlogPostsFixture.feedBlogPostList,
isLoading: false,
));
feedCubit!.lastPage = 3;
final postToUpdate = feedBlogPostsFixture.feedBlogPostList[1]; // У этого wasLikedByMe = true
final initialLikes = postToUpdate.totalLikes;
feedCubit!.updateFeedBlogPostList(postToUpdate, false);
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
final updatedPost = loadedState.feedBlogPostList.firstWhere((p) => p.id == postToUpdate.id);
expect(updatedPost.wasLikedByMe, false);
expect(updatedPost.totalLikes, initialLikes - 1);
});
test('updateFeedBlogPostList - игнорирует обновление если состояние не loaded', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
// Состояние loading
feedCubit!.emit(const FeedState.loading());
final postToUpdate = feedBlogPostsFixture.feedBlogPostList.first;
feedCubit!.updateFeedBlogPostList(postToUpdate, true);
// Состояние не должно измениться
expect(feedCubit!.state, const FeedState.loading());
});
test('updateFeedBlogPostList - игнорирует обновление если пост не найден', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
feedCubit!.emit(FeedState.loaded(
feedBlogPostList: feedBlogPostsFixture.feedBlogPostList,
isLoading: false,
));
feedCubit!.lastPage = 3;
// Создаём пост с несуществующим ID
final nonExistentPost = feedBlogPostsFixture.feedBlogPostList.first.copyWith(id: 999);
feedCubit!.updateFeedBlogPostList(nonExistentPost, true);
// Список не должен измениться
final loadedState = feedCubit!.state as FeedStateLoaded;
expect(loadedState.feedBlogPostList.length, 2);
});
});
}

View File

@@ -0,0 +1,344 @@
import 'package:eatvio_app/core/models/pagination_model.dart';
import 'package:eatvio_app/features/feed/models/comment_model.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.dart';
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/feed/services/feed_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'feed_repository_test.mocks.dart';
@GenerateNiceMocks([MockSpec<FeedService>()])
void main() {
group('FeedRepository', () {
late FeedRepository feedRepository;
late MockFeedService mockFeedService;
setUp(() {
mockFeedService = MockFeedService();
feedRepository = FeedRepository(feedService: mockFeedService);
});
test('getFeedBlogPosts - успешное получение постов', () async {
final expectedModel = FeedBlogPostSettingsModel(
feedBlogPostList: const [],
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getBlogPostList(page: 1, query: null))
.thenAnswer((_) async => expectedModel);
final result = await feedRepository.getFeedBlogPosts();
expect(result, isA<FeedBlogPostSettingsModel>());
verify(mockFeedService.getBlogPostList(page: 1, query: null)).called(1);
});
test('getFeedBlogPosts - с запросом и страницей', () async {
const query = 'тест';
const page = 2;
final expectedModel = FeedBlogPostSettingsModel(
feedBlogPostList: const [],
meta: const Meta(
currentPage: page,
from: null,
lastPage: 2,
links: [],
path: null,
perPage: 10,
to: null,
total: 15,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getBlogPostList(page: page, query: query))
.thenAnswer((_) async => expectedModel);
final result = await feedRepository.getFeedBlogPosts(query: query, page: page);
expect(result, isA<FeedBlogPostSettingsModel>());
verify(mockFeedService.getBlogPostList(page: page, query: query)).called(1);
});
test('getBlogPost - успешное получение поста', () async {
const postId = 1;
final expectedPost = FeedBlogPostModel(
id: postId,
state: 'published',
image: 'https://example.com/image.jpg',
description: 'Описание',
author: const Author(
authorSlug: 'author-slug',
authorName: 'Автор',
authorProfileIcon: 'https://example.com/photo.jpg',
authorIsMe: false,
isEntitySubscribedByMe: null,
authorPlaceInRating: null,
),
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
title: 'Заголовок',
totalLikes: 10,
totalViews: 100,
totalComments: 5,
wasLikedByMe: false,
);
when(mockFeedService.getBlogPost(postId: postId))
.thenAnswer((_) async => expectedPost);
final result = await feedRepository.getBlogPost(id: postId);
expect(result, isA<FeedBlogPostModel>());
expect(result.id, postId);
verify(mockFeedService.getBlogPost(postId: postId)).called(1);
});
test('getRecipes - успешное получение рецептов', () async {
final expectedRecipes = const RecipesPagination(
recipesList: [],
);
when(mockFeedService.getRecipes(query: null, tags: null, page: 1))
.thenAnswer((_) async => expectedRecipes);
final result = await feedRepository.getRecipes();
expect(result, isA<List<Recipe>>());
verify(mockFeedService.getRecipes(query: null, tags: null, page: 1)).called(1);
});
test('getDiets - успешное получение диет', () async {
final expectedDiets = DietsPagination(
data: const [],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getDiets(
page: 1,
query: null,
types: null,
duration: null,
sorting: null,
)).thenAnswer((_) async => expectedDiets);
final result = await feedRepository.getDiets();
expect(result, isA<DietsPagination>());
verify(mockFeedService.getDiets(
page: 1,
query: null,
types: null,
duration: null,
sorting: null,
)).called(1);
});
test('getPlans - успешное получение планов', () async {
final expectedPlans = PlanPaginationModel(
data: const [],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getPlans(
page: 1,
query: null,
types: null,
duration: null,
minPrice: null,
maxPrice: null,
)).thenAnswer((_) async => expectedPlans);
final result = await feedRepository.getPlans();
expect(result, isA<PlanPaginationModel>());
verify(mockFeedService.getPlans(
page: 1,
query: null,
types: null,
duration: null,
minPrice: null,
maxPrice: null,
)).called(1);
});
test('getBlogPostComments - успешное получение комментариев', () async {
const postId = '1';
final expectedComments = CommentModel(
data: [],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getBlogPostComments(id: postId))
.thenAnswer((_) async => expectedComments);
final result = await feedRepository.getBlogPostComments(id: postId);
expect(result, isA<CommentModel>());
verify(mockFeedService.getBlogPostComments(id: postId)).called(1);
});
test('sendBlogPostComment - успешная отправка комментария', () async {
const postId = '1';
const commentText = 'Тестовый комментарий';
when(mockFeedService.sendBlogPostComment(
id: postId,
body: commentText,
parentCommentId: null,
)).thenAnswer((_) async => Future.value());
await feedRepository.sendBlogPostComment(id: postId, commentText: commentText);
verify(mockFeedService.sendBlogPostComment(
id: postId,
body: commentText,
parentCommentId: null,
)).called(1);
});
test('likeBlogPost - успешный лайк поста', () async {
const postId = 1;
when(mockFeedService.likeBlogPost(id: postId))
.thenAnswer((_) async => Future.value());
await feedRepository.likeBlogPost(id: postId);
verify(mockFeedService.likeBlogPost(id: postId)).called(1);
});
test('unlikedBlogPost - успешное снятие лайка', () async {
const postId = 1;
when(mockFeedService.unlikedBlogPost(id: postId))
.thenAnswer((_) async => Future.value());
await feedRepository.unlikedBlogPost(id: postId);
verify(mockFeedService.unlikedBlogPost(id: postId)).called(1);
});
test('getConsultants - успешное получение консультантов', () async {
final expectedConsultants = FeedConsultantPaginationModel(
data: const [],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getConsultants(name: null))
.thenAnswer((_) async => expectedConsultants);
final result = await feedRepository.getConsultants();
expect(result, isA<List<FeedConsultantModel>>());
verify(mockFeedService.getConsultants(name: null)).called(1);
});
test('viewPost - успешное просмотр поста', () async {
const postId = 1;
when(mockFeedService.viewPost(id: postId))
.thenAnswer((_) async => Future.value());
await feedRepository.viewPost(postId);
verify(mockFeedService.viewPost(id: postId)).called(1);
});
test('viewContent - успешный просмотр контента', () async {
final viewModel = ViewModel(
type: 'blog_post',
ids: [1],
slugs: null,
);
when(mockFeedService.viewContent(viewModel: viewModel))
.thenAnswer((_) async => Future.value());
await feedRepository.viewContent(viewModel: viewModel);
verify(mockFeedService.viewContent(viewModel: viewModel)).called(1);
});
test('getFeedBlogPosts - пробрасывает исключение', () async {
when(mockFeedService.getBlogPostList(page: 1, query: null))
.thenThrow(Exception('Ошибка сети'));
expect(
() => feedRepository.getFeedBlogPosts(),
throwsException,
);
});
});
}

View File

@@ -0,0 +1,7 @@
{
"provider": "vk",
"accessToken": "vk_access_token_123",
"refreshToken": "vk_refresh_token_123",
"clientId": "vk_client_id_123"
}

View File

@@ -0,0 +1,16 @@
{
"provider": "vk",
"accessToken": "vk_access_token_123",
"id": "vk_user_id_123",
"refreshToken": "vk_refresh_token_123",
"firstName": "Иван",
"lastName": "Иванов",
"email": "ivan@example.com",
"phone": "+79001234567",
"dateOfBirth": "1990-01-01",
"gender": "М",
"weight": 75,
"height": 180,
"clientId": "vk_client_id_123"
}

View File

@@ -0,0 +1,7 @@
{
"email": "user@example.com",
"code": "123456",
"password": "newPassword123",
"passwordConfirmation": "newPassword123"
}

View File

@@ -0,0 +1,33 @@
{
"feedBlogPostList": [
{
"id": 1,
"title": "Тестовый пост",
"content": "Содержимое поста",
"author": "Автор",
"createdAt": "2024-01-01T00:00:00Z",
"totalLikes": 10,
"wasLikedByMe": false,
"totalComments": 5,
"totalViews": 100
},
{
"id": 2,
"title": "Второй пост",
"content": "Еще одно содержимое",
"author": "Другой автор",
"createdAt": "2024-01-02T00:00:00Z",
"totalLikes": 20,
"wasLikedByMe": true,
"totalComments": 10,
"totalViews": 200
}
],
"meta": {
"currentPage": 1,
"lastPage": 3,
"perPage": 10,
"total": 25
}
}