feat(sbp_payments): added cubit layer for sbp payments with tests

This commit is contained in:
misha
2026-07-09 18:35:09 +03:00
parent a0edfe942b
commit be924265a1
7 changed files with 422 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
import 'package:eatvio_app/features/chat/models/payment_model.dart';
import 'package:eatvio_app/features/payment/presentation/cubit/consultation_payment_cubit/consultation_payment_cubit.dart';
import 'package:eatvio_app/features/payment/presentation/cubit/consultation_payment_cubit/consultation_payment_state.dart';
import 'package:eatvio_app/features/payment/repositories/payment_repository.dart';
import 'package:eatvio_app/features/payment/repositories/sbp_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:sbp/models/c2bmembers_model.dart';
import '../../../../fixtures/fixture_reader.dart';
import 'consultation_payment_cubit_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<PaymentRepository>(),
MockSpec<SbpRepository>(),
])
void main() {
group('ConsultationPaymentCubit', () {
late ConsultationPaymentCubit cubit;
late MockPaymentRepository mockPaymentRepository;
late MockSbpRepository mockSbpRepository;
final installedBank = C2bmemberModel.fromJson(const {
'bankName': 'Сбербанк',
'logoURL': 'https://qr.nspk.ru/proxyapp/logo/bank100000000111.png',
'schema': 'bank100000000111',
'package_name': 'ru.sberbankmobile',
'icon': 'packages/sbp/assets/icons/sber.png',
});
late PaymentModel alfaPayment;
setUp(() {
mockPaymentRepository = MockPaymentRepository();
mockSbpRepository = MockSbpRepository();
cubit = ConsultationPaymentCubit(
paymentRepository: mockPaymentRepository,
sbpRepository: mockSbpRepository,
);
alfaPayment = PaymentModel.fromJson(
FixtureReader.readAsMap('chat/payments/payment_response_alfa_bank.json'),
);
});
tearDown(() {
cubit.close();
});
test('начальный state - isLoading=false, banks=[], checkoutUrl=null', () {
expect(cubit.state, isA<ConsultationPaymentState>());
expect(cubit.state.isLoading, isFalse);
expect(cubit.state.banks, isEmpty);
expect(cubit.state.checkoutUrl, isNull);
expect(cubit.state.orderId, isNull);
expect(cubit.state.errorMessage, isNull);
});
test(
'startPayment - успешно получает checkout_url и загружает банки',
() async {
const planId = 123;
when(
mockPaymentRepository.startPaymentProcess(planId: planId),
).thenAnswer((_) async => alfaPayment);
when(
mockSbpRepository.getInstalledBanks(),
).thenAnswer((_) async => [installedBank]);
await cubit.startPayment(planId: planId);
expect(cubit.state.isLoading, isFalse);
expect(cubit.state.checkoutUrl, alfaPayment.paymentUrl);
expect(cubit.state.orderId, alfaPayment.orderId);
expect(cubit.state.banks, [installedBank]);
expect(cubit.state.errorMessage, isNull);
verify(
mockPaymentRepository.startPaymentProcess(planId: planId),
).called(1);
verify(mockSbpRepository.getInstalledBanks()).called(1);
},
);
test('startPayment - при ошибке API emit errorMessage', () async {
const planId = 123;
when(
mockPaymentRepository.startPaymentProcess(planId: planId),
).thenThrow(Exception('Payment start failed'));
await cubit.startPayment(planId: planId);
expect(cubit.state.isLoading, isFalse);
expect(cubit.state.errorMessage, isNotNull);
expect(cubit.state.banks, isEmpty);
expect(cubit.state.checkoutUrl, isNull);
verifyNever(mockSbpRepository.getInstalledBanks());
});
test(
'startPayment - при ошибке SBP после успешного payment emit error',
() async {
const planId = 123;
when(
mockPaymentRepository.startPaymentProcess(planId: planId),
).thenAnswer((_) async => alfaPayment);
when(
mockSbpRepository.getInstalledBanks(),
).thenThrow(Exception('SBP plugin error'));
await cubit.startPayment(planId: planId);
expect(cubit.state.isLoading, isFalse);
expect(cubit.state.errorMessage, isNotNull);
expect(cubit.state.checkoutUrl, alfaPayment.paymentUrl);
expect(cubit.state.orderId, alfaPayment.orderId);
expect(cubit.state.banks, isEmpty);
},
);
test('openBank - успешно открывает банк после startPayment', () async {
const planId = 123;
when(
mockPaymentRepository.startPaymentProcess(planId: planId),
).thenAnswer((_) async => alfaPayment);
when(
mockSbpRepository.getInstalledBanks(),
).thenAnswer((_) async => [installedBank]);
when(
mockSbpRepository.openBank(
url: alfaPayment.paymentUrl,
bank: installedBank,
),
).thenAnswer((_) async => true);
await cubit.startPayment(planId: planId);
final result = await cubit.openBank(bank: installedBank);
expect(result, isTrue);
verify(
mockSbpRepository.openBank(
url: alfaPayment.paymentUrl,
bank: installedBank,
),
).called(1);
});
test(
'openBank - без checkoutUrl возвращает false и не вызывает SbpRepository',
() async {
final result = await cubit.openBank(bank: installedBank);
expect(result, isFalse);
verifyNever(
mockSbpRepository.openBank(
url: anyNamed('url'),
bank: anyNamed('bank'),
),
);
},
);
test('openBank - возвращает false когда банк не открылся', () async {
const planId = 123;
when(
mockPaymentRepository.startPaymentProcess(planId: planId),
).thenAnswer((_) async => alfaPayment);
when(
mockSbpRepository.getInstalledBanks(),
).thenAnswer((_) async => [installedBank]);
when(
mockSbpRepository.openBank(
url: alfaPayment.paymentUrl,
bank: installedBank,
),
).thenAnswer((_) async => false);
await cubit.startPayment(planId: planId);
final result = await cubit.openBank(bank: installedBank);
expect(result, isFalse);
});
});
}

View File

@@ -0,0 +1,97 @@
import 'package:eatvio_app/features/chat/models/payment_model.dart';
import 'package:eatvio_app/features/chat/services/chat_service.dart';
import 'package:eatvio_app/features/payment/repositories/payment_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 'payment_repository_test.mocks.dart';
@GenerateNiceMocks([MockSpec<ChatService>()])
void main() {
group('PaymentRepository', () {
late PaymentRepository paymentRepository;
late MockChatService mockChatService;
setUp(() {
mockChatService = MockChatService();
paymentRepository = PaymentRepository(chatService: mockChatService);
});
test(
'startPaymentProcess - возвращает PaymentModel из ответа alfa-bank',
() async {
const planId = 123;
final paymentFixture = FixtureReader.readAsMap(
'chat/payments/payment_response_alfa_bank.json',
);
final expectedPayment = PaymentModel.fromJson(paymentFixture);
final requestBody = Map<String, String>.from(
FixtureReader.readAsMap('chat/payments/payment_alfabank_request.json'),
);
when(
mockChatService.getPaymentLink(id: planId, body: requestBody),
).thenAnswer((_) async => expectedPayment);
final result = await paymentRepository.startPaymentProcess(planId: planId);
expect(result, isA<PaymentModel>());
expect(result.paymentUrl, 'https://qr.nspk.ru/ASDFASDFASDFASDF');
expect(result.orderId, 'alfa_c29_k15138_t1712345678_r1234');
expect(result.paymentProvider, 'alfa-bank');
verify(
mockChatService.getPaymentLink(
id: planId,
body: {'payment_provider': 'alfa-bank'},
),
).called(1);
},
);
test(
'startPaymentProcess - передаёт payment_provider alfa-bank в тело запроса',
() async {
const planId = 42;
final paymentFixture = FixtureReader.readAsMap(
'chat/payments/payment_response_alfa_bank.json',
);
final expectedPayment = PaymentModel.fromJson(paymentFixture);
when(
mockChatService.getPaymentLink(
id: planId,
body: {'payment_provider': 'alfa-bank'},
),
).thenAnswer((_) async => expectedPayment);
await paymentRepository.startPaymentProcess(planId: planId);
verify(
mockChatService.getPaymentLink(
id: planId,
body: {'payment_provider': 'alfa-bank'},
),
).called(1);
},
);
test('startPaymentProcess - пробрасывает ошибку сервиса', () async {
const planId = 123;
final error = Exception('Payment start failed');
when(
mockChatService.getPaymentLink(
id: planId,
body: {'payment_provider': 'alfa-bank'},
),
).thenThrow(error);
expect(
() => paymentRepository.startPaymentProcess(planId: planId),
throwsA(isA<Exception>()),
);
});
});
}