forked from eatvio/eatvio_app
Merge pull request 'feat(consultant_scoring)' (#1012) from consultant_scoring into main
Reviewed-on: eatvio/eatvio_app#1012
This commit is contained in:
@@ -33,4 +33,3 @@ void main() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -100,4 +100,3 @@ void main() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,10 @@ void main() {
|
||||
});
|
||||
|
||||
test('toJson serializes all fields correctly', () {
|
||||
const model = CertificatesModel(title: 'Certificate Title', date: '2024-01-15');
|
||||
const model = CertificatesModel(
|
||||
title: 'Certificate Title',
|
||||
date: '2024-01-15',
|
||||
);
|
||||
|
||||
final json = model.toJson();
|
||||
|
||||
@@ -105,8 +108,21 @@ void main() {
|
||||
|
||||
test('toJson serializes all fields correctly', () {
|
||||
const socialLinks = SocialLinksModel(vkLink: 'https://vk.com/user');
|
||||
const certificates = CertificatesModel(title: 'Course 1', date: '2024-01-01');
|
||||
const model = ConsultantProfileModel(hasMedicalEducation: true, socialLinks: socialLinks, photos: ['photo1.jpg'], certificates: [certificates]);
|
||||
const certificates = CertificatesModel(
|
||||
title: 'Course 1',
|
||||
date: '2024-01-01',
|
||||
);
|
||||
const model = ConsultantProfileModel(
|
||||
hasMedicalEducation: true,
|
||||
socialLinks: socialLinks,
|
||||
photos: ['photo1.jpg'],
|
||||
certificates: [certificates],
|
||||
legalType: 'individualPerson',
|
||||
inn: '123456789012',
|
||||
snils: '123-456-789 00',
|
||||
agreementAccepted: true,
|
||||
frdoAgreementAccepted: true,
|
||||
);
|
||||
|
||||
final json = model.toJson();
|
||||
|
||||
@@ -114,6 +130,34 @@ void main() {
|
||||
expect(json['social_links'], isNotNull);
|
||||
expect(json['cert_photos'], ['photo1.jpg']);
|
||||
expect(json['completed_courses'], isNotNull);
|
||||
expect(json['legal_type'], 'individualPerson');
|
||||
expect(json['inn'], '123456789012');
|
||||
expect(json['snils'], '123-456-789 00');
|
||||
expect(json['agreement_accepted'], isTrue);
|
||||
expect(json['frdo_agreement_accepted'], isTrue);
|
||||
});
|
||||
|
||||
test('hasValidPassportData validates required individualPerson fields', () {
|
||||
const valid = ConsultantProfileModel(
|
||||
lastName: 'Иванов',
|
||||
firstName: 'Иван',
|
||||
inn: '123456789012',
|
||||
snils: '123-456-789 00',
|
||||
passportSeries: '1234',
|
||||
passportNumber: '567890',
|
||||
passportIssueDate: '2020-01-15',
|
||||
passportIssuer: 'ОВД',
|
||||
passportIssuerCode: '772-053',
|
||||
birthDate: '1990-05-20',
|
||||
birthPlace: 'Москва',
|
||||
registrationAddress: 'Адрес 1',
|
||||
residenceAddress: 'Адрес 2',
|
||||
citizenship: 'RU',
|
||||
);
|
||||
|
||||
expect(valid.hasValidPassportData, isTrue);
|
||||
expect(valid.copyWith(inn: '123').hasValidPassportData, isFalse);
|
||||
expect(valid.copyWith(lastName: '').hasValidPassportData, isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/enums/alfa_scoring_status.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -46,7 +47,11 @@ void main() {
|
||||
});
|
||||
|
||||
test('fromJson handles null additional field', () {
|
||||
final json = {'title': 'Test title', 'status': 'canceled', 'additional': null};
|
||||
final json = {
|
||||
'title': 'Test title',
|
||||
'status': 'canceled',
|
||||
'additional': null,
|
||||
};
|
||||
|
||||
final model = CanceledModerationModel.fromJson(json);
|
||||
|
||||
@@ -56,7 +61,11 @@ void main() {
|
||||
});
|
||||
|
||||
test('toJson serializes all fields correctly', () {
|
||||
const model = CanceledModerationModel(title: 'Test title', status: ModerationStatus.canceled, additional: ['reason1', 'reason2']);
|
||||
const model = CanceledModerationModel(
|
||||
title: 'Test title',
|
||||
status: ModerationStatus.canceled,
|
||||
additional: ['reason1', 'reason2'],
|
||||
);
|
||||
|
||||
final json = model.toJson();
|
||||
|
||||
@@ -122,10 +131,67 @@ void main() {
|
||||
expect(model.canceledModeration!.status, ModerationStatus.canceled);
|
||||
});
|
||||
|
||||
test('fromJson parses legal_entity_resource', () {
|
||||
final json = {
|
||||
'rating_place': 5,
|
||||
'education_resource': {'request_state': 'approved'},
|
||||
'cert_photos': [],
|
||||
'legal_entity_resource': {
|
||||
'id': 1,
|
||||
'type': 'individualPerson',
|
||||
'inn': '123456789012',
|
||||
'snils': '123-456-789 00',
|
||||
'state': 'created',
|
||||
'alfa_scoring_status': 'scoring_failed',
|
||||
'alfa_scoring_request_id': 'abc',
|
||||
'passport_series': '1234',
|
||||
'passport_number': '567890',
|
||||
'passport_issue_date': '2020-01-15T00:00:00.000000Z',
|
||||
'passport_issuer': 'ОВД',
|
||||
'passport_issuer_code': '772-053',
|
||||
'birth_date': '1990-05-20',
|
||||
'birth_place': 'Москва',
|
||||
'registration_address': 'Адрес 1',
|
||||
'residence_address': 'Адрес 2',
|
||||
},
|
||||
};
|
||||
|
||||
final model = ConsultationRequestModel.fromJson(json);
|
||||
|
||||
expect(model.legalEntity, isNotNull);
|
||||
expect(model.legalEntity!.type, 'individualPerson');
|
||||
expect(model.legalEntity!.inn, '123456789012');
|
||||
expect(model.legalEntity!.passportSeries, '1234');
|
||||
expect(model.legalEntity!.normalizedPassportIssueDate, '2020-01-15');
|
||||
expect(
|
||||
model.legalEntity!.alfaScoringStatus,
|
||||
AlfaScoringStatus.scoringFailed,
|
||||
);
|
||||
});
|
||||
|
||||
test('normalizeApiDate strips time from DateTime.toString format', () {
|
||||
expect(
|
||||
LegalEntityModel.normalizeApiDate('2023-09-22 00:00:00.000'),
|
||||
'2023-09-22',
|
||||
);
|
||||
expect(LegalEntityModel.normalizeApiDate('2023-09-22'), '2023-09-22');
|
||||
expect(LegalEntityModel.normalizeApiDate(null), isNull);
|
||||
});
|
||||
|
||||
test('toJson serializes ConsultationRequestModel correctly', () {
|
||||
const educationModel = EducationModel(requestState: ModerationStatus.approved);
|
||||
const photos = [CertPhotosModel(url: 'https://example.com/photo1.jpg'), CertPhotosModel(url: 'https://example.com/photo2.jpg')];
|
||||
const model = ConsultationRequestModel(ratingPlace: 5, educationModel: educationModel, photos: photos, canceledModeration: null);
|
||||
const educationModel = EducationModel(
|
||||
requestState: ModerationStatus.approved,
|
||||
);
|
||||
const photos = [
|
||||
CertPhotosModel(url: 'https://example.com/photo1.jpg'),
|
||||
CertPhotosModel(url: 'https://example.com/photo2.jpg'),
|
||||
];
|
||||
const model = ConsultationRequestModel(
|
||||
ratingPlace: 5,
|
||||
educationModel: educationModel,
|
||||
photos: photos,
|
||||
canceledModeration: null,
|
||||
);
|
||||
|
||||
final json = model.toJson();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ void main() {
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
requestModel: null,
|
||||
isUpdating: false
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
expect(state.consultant, consultant);
|
||||
@@ -20,6 +20,8 @@ void main() {
|
||||
expect(state.loading, false);
|
||||
expect(state.requestModel, isNull);
|
||||
expect(state.certPhotos, isEmpty);
|
||||
expect(state.awaitingScoringOutcome, isFalse);
|
||||
expect(state.fieldErrors, isEmpty);
|
||||
});
|
||||
|
||||
test('creates instance with custom values', () {
|
||||
@@ -38,7 +40,7 @@ void main() {
|
||||
loading: true,
|
||||
requestModel: null,
|
||||
certPhotos: [certPhoto],
|
||||
isUpdating: false
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
expect(state.acceptOffer, true);
|
||||
@@ -54,7 +56,7 @@ void main() {
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
requestModel: null,
|
||||
isUpdating: false
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
final updatedState = originalState.copyWith(
|
||||
@@ -92,7 +94,7 @@ void main() {
|
||||
loading: false,
|
||||
requestModel: null,
|
||||
certPhotos: [certPhoto1],
|
||||
isUpdating: false
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
final updatedState = originalState.copyWith(
|
||||
@@ -100,9 +102,14 @@ void main() {
|
||||
);
|
||||
|
||||
expect(updatedState.certPhotos.length, 2);
|
||||
expect(updatedState.certPhotos.first.url, 'https://example.com/photo1.jpg');
|
||||
expect(updatedState.certPhotos.last.url, 'https://example.com/photo2.jpg');
|
||||
expect(
|
||||
updatedState.certPhotos.first.url,
|
||||
'https://example.com/photo1.jpg',
|
||||
);
|
||||
expect(
|
||||
updatedState.certPhotos.last.url,
|
||||
'https://example.com/photo2.jpg',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/enums/alfa_scoring_status.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/sbp_bank_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -23,16 +26,50 @@ void main() {
|
||||
late RegisterConsultantCubit cubit;
|
||||
late ConsultationRequestModel requestModel;
|
||||
late List<ConsultantCertPhotoModel> certPhotosFromFixture;
|
||||
late List<SbpBankModel> sbpBanksFromFixture;
|
||||
late ConsultantProfileModel consultantProfile;
|
||||
|
||||
ConsultationRequestModel requestWithScoring(String scoringStatus) {
|
||||
final json = FixtureReader.readAsMap(
|
||||
'consultant/consultation_request.json',
|
||||
);
|
||||
final legal = Map<String, dynamic>.from(
|
||||
json['legal_entity_resource'] as Map,
|
||||
);
|
||||
legal['alfa_scoring_status'] = scoringStatus;
|
||||
json['legal_entity_resource'] = legal;
|
||||
return ConsultationRequestModel.fromJson(json);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
repository = MockConsultantRepository();
|
||||
cubit = RegisterConsultantCubit(consultantRepository: repository);
|
||||
requestModel = ConsultationRequestModel.fromJson(FixtureReader.readAsMap('consultant/consultation_request.json'));
|
||||
certPhotosFromFixture = FixtureReader.readAsList(
|
||||
'consultant/consultant_cert_photos.json',
|
||||
).map((item) => ConsultantCertPhotoModel.fromJson(item as Map<String, dynamic>)).toList();
|
||||
consultantProfile = ConsultantProfileModel.fromJson(FixtureReader.readAsMap('consultant/consultant_profile_request.json'));
|
||||
cubit = RegisterConsultantCubit(
|
||||
consultantRepository: repository,
|
||||
scoringPollInterval: const Duration(milliseconds: 30),
|
||||
);
|
||||
requestModel = ConsultationRequestModel.fromJson(
|
||||
FixtureReader.readAsMap('consultant/consultation_request.json'),
|
||||
);
|
||||
certPhotosFromFixture =
|
||||
FixtureReader.readAsList('consultant/consultant_cert_photos.json')
|
||||
.map(
|
||||
(item) => ConsultantCertPhotoModel.fromJson(
|
||||
item as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
sbpBanksFromFixture =
|
||||
FixtureReader.readAsList('consultant/sbp_banks.json')
|
||||
.map(
|
||||
(item) => SbpBankModel.fromJson(item as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
consultantProfile = ConsultantProfileModel.fromJson(
|
||||
FixtureReader.readAsMap('consultant/consultant_profile_request.json'),
|
||||
);
|
||||
when(
|
||||
() => repository.getSbpBanks(),
|
||||
).thenAnswer((_) async => sbpBanksFromFixture);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
@@ -44,11 +81,16 @@ void main() {
|
||||
expect(cubit.state.consultant.hasMedicalEducation, isTrue);
|
||||
expect(cubit.state.acceptOffer, isFalse);
|
||||
expect(cubit.state.requestModel, isNull);
|
||||
expect(cubit.state.alfaScoringStatus, AlfaScoringStatus.none);
|
||||
});
|
||||
|
||||
test('getMyRequestStatus hydrates state with backend data', () async {
|
||||
when(() => repository.getMyRequestStatus(any<String>())).thenAnswer((_) async => requestModel);
|
||||
when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture);
|
||||
when(
|
||||
() => repository.getMyRequestStatus(any<String>()),
|
||||
).thenAnswer((_) async => requestModel);
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
|
||||
await cubit.getMyRequestStatus('my-consultant-slug');
|
||||
|
||||
@@ -56,53 +98,308 @@ void main() {
|
||||
expect(state.loading, isFalse);
|
||||
expect(state.requestModel, equals(requestModel));
|
||||
expect(state.consultant.certificates, isNotNull);
|
||||
// Certificates list is built from backend courses; we just ensure it is not empty.
|
||||
expect(state.consultant.certificates!.length, greaterThan(0));
|
||||
expect(state.consultant.certificates!.first.title, 'Нутрициология PRO');
|
||||
expect(state.consultant.socialLinks?.instaLink, isNull, reason: 'empty links are sanitized');
|
||||
expect(
|
||||
state.consultant.socialLinks?.instaLink,
|
||||
isNull,
|
||||
reason: 'empty links are sanitized',
|
||||
);
|
||||
expect(state.consultant.socialLinks?.vkLink, 'https://vk.com/consultant');
|
||||
// All cert photos from backend are kept in state (visible + hidden).
|
||||
expect(state.consultant.legalType, 'individualPerson');
|
||||
expect(state.consultant.inn, '123456789012');
|
||||
expect(state.consultant.passportSeries, '1234');
|
||||
expect(state.consultant.passportIssueDate, '2020-01-15');
|
||||
expect(state.consultant.birthPlace, 'г. Москва');
|
||||
expect(state.certPhotos, hasLength(3));
|
||||
expect(state.certPhotos.map((e) => e.id), containsAll(<int>[101, 102, 103]));
|
||||
verify(() => repository.getMyRequestStatus('my-consultant-slug')).called(1);
|
||||
expect(
|
||||
state.certPhotos.map((e) => e.id),
|
||||
containsAll(<int>[101, 102, 103]),
|
||||
);
|
||||
expect(state.sbpBanks, hasLength(2));
|
||||
expect(state.sbpBanks.first.bankName, 'Сбербанк');
|
||||
expect(state.alfaScoringStatus, AlfaScoringStatus.scoringFailed);
|
||||
expect(state.alfaScoringStatus.canRetry, isTrue);
|
||||
expect(state.isScoringPolling, isFalse);
|
||||
expect(
|
||||
state.awaitingScoringOutcome,
|
||||
isFalse,
|
||||
reason: 'hydrate must not arm success sheet',
|
||||
);
|
||||
verify(
|
||||
() => repository.getMyRequestStatus('my-consultant-slug'),
|
||||
).called(1);
|
||||
verify(() => repository.getConsultantCertPhotos()).called(1);
|
||||
verify(() => repository.getSbpBanks()).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
'getMyRequestStatus with processing status starts scoring poll',
|
||||
() async {
|
||||
final pending = requestWithScoring('scoring_pending');
|
||||
final complete = requestWithScoring('scoring_complete');
|
||||
var calls = 0;
|
||||
when(() => repository.getMyRequestStatus(any<String>())).thenAnswer((
|
||||
_,
|
||||
) async {
|
||||
calls++;
|
||||
return calls == 1 ? pending : complete;
|
||||
});
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
|
||||
await cubit.getMyRequestStatus('my-consultant-slug');
|
||||
|
||||
expect(cubit.state.alfaScoringStatus, AlfaScoringStatus.scoringPending);
|
||||
expect(cubit.state.isScoringPolling, isTrue);
|
||||
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
expect(
|
||||
cubit.state.alfaScoringStatus,
|
||||
AlfaScoringStatus.scoringComplete,
|
||||
);
|
||||
expect(cubit.state.isScoringPolling, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
test('disableLoading stops spinner and loads cert photos', () async {
|
||||
when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture);
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
|
||||
await cubit.disableLoading(slug: 'my-consultant-slug');
|
||||
|
||||
expect(cubit.state.loading, isFalse);
|
||||
expect(cubit.state.certPhotos, hasLength(3));
|
||||
expect(cubit.state.sbpBanks, hasLength(2));
|
||||
expect(cubit.userSlug, 'my-consultant-slug');
|
||||
verify(() => repository.getConsultantCertPhotos()).called(1);
|
||||
verify(() => repository.getSbpBanks()).called(1);
|
||||
});
|
||||
|
||||
test('searchSbpBanks filters banks by name', () async {
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
await cubit.disableLoading();
|
||||
|
||||
expect(cubit.state.loading, isFalse);
|
||||
// disableLoading now keeps all photos from repository.
|
||||
expect(cubit.state.certPhotos, hasLength(3));
|
||||
verify(() => repository.getConsultantCertPhotos()).called(1);
|
||||
final results = cubit.searchSbpBanks('сбер');
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.bankMemberId, '100000000111');
|
||||
});
|
||||
|
||||
test('updateConsultantProfileModel replaces consultant data and offer flag', () {
|
||||
cubit.updateConsultantProfileModel(consultantProfile, true);
|
||||
|
||||
expect(cubit.state.consultant, consultantProfile);
|
||||
expect(cubit.state.acceptOffer, isTrue);
|
||||
expect(cubit.state.loading, isFalse);
|
||||
});
|
||||
|
||||
test('registerConsultant forwards payload with update=false by default', () async {
|
||||
test('findSbpBankById returns matching bank', () async {
|
||||
when(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: any<bool>(named: 'update'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
await cubit.disableLoading();
|
||||
|
||||
await cubit.registerConsultant();
|
||||
final bank = cubit.findSbpBankById('100000000004');
|
||||
|
||||
verify(() => repository.registerConsultant(consultant: cubit.state.consultant, update: false)).called(1);
|
||||
expect(bank?.bankName, 'Т-Банк');
|
||||
});
|
||||
|
||||
test('registerConsultant sends update=true after getMyRequestStatus call', () async {
|
||||
when(() => repository.getMyRequestStatus(any<String>())).thenAnswer((_) async => requestModel);
|
||||
when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture);
|
||||
test(
|
||||
'updateConsultantProfileModel replaces consultant data and offer flag',
|
||||
() {
|
||||
cubit.updateConsultantProfileModel(consultantProfile, true);
|
||||
|
||||
expect(cubit.state.consultant, consultantProfile);
|
||||
expect(cubit.state.acceptOffer, isTrue);
|
||||
expect(cubit.state.loading, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'registerConsultant forwards payload with update=false by default',
|
||||
() async {
|
||||
when(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: any<bool>(named: 'update'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
cubit.updateConsultantProfileModel(consultantProfile);
|
||||
cubit.updateAgreements(acceptOffer: true, acceptFrdoAgreement: true);
|
||||
await cubit.registerConsultant();
|
||||
|
||||
final captured =
|
||||
verify(
|
||||
() => repository.registerConsultant(
|
||||
consultant: captureAny<ConsultantProfileModel>(
|
||||
named: 'consultant',
|
||||
),
|
||||
update: false,
|
||||
),
|
||||
).captured.single
|
||||
as ConsultantProfileModel;
|
||||
|
||||
expect(captured.legalType, 'individualPerson');
|
||||
expect(captured.agreementAccepted, isTrue);
|
||||
expect(captured.frdoAgreementAccepted, isTrue);
|
||||
expect(captured.inn, isNotNull);
|
||||
expect(
|
||||
cubit.state.alfaScoringStatus,
|
||||
AlfaScoringStatus.beneficiaryCreating,
|
||||
);
|
||||
expect(
|
||||
cubit.state.isScoringPolling,
|
||||
isFalse,
|
||||
reason: 'no slug for poll',
|
||||
);
|
||||
expect(
|
||||
cubit.state.awaitingScoringOutcome,
|
||||
isFalse,
|
||||
reason: 'cannot await scoring without slug',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'getMyRequestStatus with scoring_complete does not arm awaiting flag',
|
||||
() async {
|
||||
when(
|
||||
() => repository.getMyRequestStatus(any<String>()),
|
||||
).thenAnswer((_) async => requestWithScoring('scoring_complete'));
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
|
||||
await cubit.getMyRequestStatus('my-consultant-slug');
|
||||
|
||||
expect(
|
||||
cubit.state.alfaScoringStatus,
|
||||
AlfaScoringStatus.scoringComplete,
|
||||
);
|
||||
expect(cubit.state.awaitingScoringOutcome, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'registerConsultant with slug starts scoring poll after submit',
|
||||
() async {
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
when(
|
||||
() => repository.getMyRequestStatus(any<String>()),
|
||||
).thenAnswer((_) async => requestWithScoring('scoring_complete'));
|
||||
when(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: any<bool>(named: 'update'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await cubit.disableLoading(slug: 'my-consultant-slug');
|
||||
cubit.updateConsultantProfileModel(consultantProfile);
|
||||
cubit.updateAgreements(acceptOffer: true, acceptFrdoAgreement: true);
|
||||
await cubit.registerConsultant();
|
||||
|
||||
expect(
|
||||
cubit.state.alfaScoringStatus,
|
||||
AlfaScoringStatus.beneficiaryCreating,
|
||||
);
|
||||
expect(cubit.state.isScoringPolling, isTrue);
|
||||
expect(cubit.state.awaitingScoringOutcome, isTrue);
|
||||
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
expect(
|
||||
cubit.state.alfaScoringStatus,
|
||||
AlfaScoringStatus.scoringComplete,
|
||||
);
|
||||
expect(cubit.state.isScoringPolling, isFalse);
|
||||
expect(
|
||||
cubit.state.awaitingScoringOutcome,
|
||||
isFalse,
|
||||
reason: 'flag clears on terminal scoring for one-shot success sheet',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'scoring poll failed clears awaiting flag without leaving processing',
|
||||
() async {
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
when(
|
||||
() => repository.getMyRequestStatus(any<String>()),
|
||||
).thenAnswer((_) async => requestWithScoring('scoring_failed'));
|
||||
when(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: any<bool>(named: 'update'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await cubit.disableLoading(slug: 'my-consultant-slug');
|
||||
cubit.updateConsultantProfileModel(consultantProfile);
|
||||
cubit.updateAgreements(acceptOffer: true, acceptFrdoAgreement: true);
|
||||
await cubit.registerConsultant();
|
||||
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
expect(cubit.state.alfaScoringStatus, AlfaScoringStatus.scoringFailed);
|
||||
expect(cubit.state.isScoringPolling, isFalse);
|
||||
expect(cubit.state.awaitingScoringOutcome, isFalse);
|
||||
expect(cubit.state.alfaScoringStatus.canRetry, isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'scoring poll keeps processing UI while API still returns none',
|
||||
() async {
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
var calls = 0;
|
||||
when(() => repository.getMyRequestStatus(any<String>())).thenAnswer((
|
||||
_,
|
||||
) async {
|
||||
calls++;
|
||||
if (calls <= 2) {
|
||||
return requestWithScoring('none');
|
||||
}
|
||||
return requestWithScoring('scoring_complete');
|
||||
});
|
||||
when(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: any<bool>(named: 'update'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await cubit.disableLoading(slug: 'my-consultant-slug');
|
||||
cubit.updateConsultantProfileModel(consultantProfile);
|
||||
cubit.updateAgreements(acceptOffer: true, acceptFrdoAgreement: true);
|
||||
await cubit.registerConsultant();
|
||||
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
expect(
|
||||
cubit.state.alfaScoringStatus,
|
||||
AlfaScoringStatus.beneficiaryCreating,
|
||||
);
|
||||
expect(cubit.state.isScoringPolling, isTrue);
|
||||
|
||||
await Future<void>.delayed(const Duration(milliseconds: 80));
|
||||
expect(
|
||||
cubit.state.alfaScoringStatus,
|
||||
AlfaScoringStatus.scoringComplete,
|
||||
);
|
||||
expect(cubit.state.isScoringPolling, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
test('registerConsultant sends legal payload with update=true', () async {
|
||||
when(
|
||||
() => repository.getMyRequestStatus(any<String>()),
|
||||
).thenAnswer((_) async => requestModel);
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
when(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
@@ -113,21 +410,131 @@ void main() {
|
||||
await cubit.getMyRequestStatus('my-consultant-slug');
|
||||
await cubit.registerConsultant();
|
||||
|
||||
verify(() => repository.registerConsultant(consultant: cubit.state.consultant, update: true)).called(1);
|
||||
final captured =
|
||||
verify(
|
||||
() => repository.registerConsultant(
|
||||
consultant: captureAny<ConsultantProfileModel>(
|
||||
named: 'consultant',
|
||||
),
|
||||
update: true,
|
||||
),
|
||||
).captured.single
|
||||
as ConsultantProfileModel;
|
||||
|
||||
final json = captured.toJson();
|
||||
expect(json['legal_type'], 'individualPerson');
|
||||
expect(json['inn'], '123456789012');
|
||||
expect(json['passport_series'], '1234');
|
||||
expect(json.containsKey('agreement_accepted'), isFalse);
|
||||
expect(json.containsKey('frdo_agreement_accepted'), isFalse);
|
||||
});
|
||||
|
||||
test('retryScoring calls update and restarts poll', () async {
|
||||
when(
|
||||
() => repository.getMyRequestStatus(any<String>()),
|
||||
).thenAnswer((_) async => requestModel);
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
when(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: any<bool>(named: 'update'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await cubit.getMyRequestStatus('my-consultant-slug');
|
||||
expect(cubit.state.alfaScoringStatus, AlfaScoringStatus.scoringFailed);
|
||||
|
||||
when(
|
||||
() => repository.getMyRequestStatus(any<String>()),
|
||||
).thenAnswer((_) async => requestWithScoring('scoring_complete'));
|
||||
await cubit.retryScoring();
|
||||
|
||||
verify(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: true,
|
||||
),
|
||||
).called(1);
|
||||
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
expect(cubit.state.alfaScoringStatus, AlfaScoringStatus.scoringComplete);
|
||||
expect(cubit.state.isScoringPolling, isFalse);
|
||||
});
|
||||
|
||||
test('registerConsultant blocks submit without passport data', () async {
|
||||
when(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: any<bool>(named: 'update'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await cubit.registerConsultant();
|
||||
|
||||
expect(cubit.state.error, 'Заполните паспортные данные');
|
||||
verifyNever(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: any<bool>(named: 'update'),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('registerConsultant maps Laravel 422 into fieldErrors', () async {
|
||||
when(
|
||||
() => repository.registerConsultant(
|
||||
consultant: any<ConsultantProfileModel>(named: 'consultant'),
|
||||
update: any<bool>(named: 'update'),
|
||||
),
|
||||
).thenThrow(
|
||||
DioException(
|
||||
requestOptions: RequestOptions(
|
||||
path: '/api/v2/consultant_request/registration',
|
||||
),
|
||||
response: Response(
|
||||
requestOptions: RequestOptions(
|
||||
path: '/api/v2/consultant_request/registration',
|
||||
),
|
||||
statusCode: 422,
|
||||
data: {
|
||||
'message': 'The given data was invalid.',
|
||||
'errors': {
|
||||
'inn': ['The inn format is invalid.'],
|
||||
},
|
||||
},
|
||||
),
|
||||
type: DioExceptionType.badResponse,
|
||||
),
|
||||
);
|
||||
|
||||
cubit.updateConsultantProfileModel(consultantProfile);
|
||||
cubit.updateAgreements(acceptOffer: true, acceptFrdoAgreement: true);
|
||||
await cubit.registerConsultant();
|
||||
|
||||
expect(cubit.state.fieldErrors['inn'], 'Некорректный ИНН');
|
||||
expect(cubit.state.error, contains('Некорректный ИНН'));
|
||||
expect(cubit.state.awaitingScoringOutcome, isFalse);
|
||||
expect(cubit.state.isScoringPolling, isFalse);
|
||||
});
|
||||
|
||||
test('removeCertPhoto marks cert as deleted locally', () async {
|
||||
when(() => repository.getMyRequestStatus(any<String>())).thenAnswer((_) async => requestModel);
|
||||
when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture);
|
||||
when(() => repository.hideConsultantCertPhoto(any<int>())).thenAnswer((_) async {});
|
||||
when(
|
||||
() => repository.getMyRequestStatus(any<String>()),
|
||||
).thenAnswer((_) async => requestModel);
|
||||
when(
|
||||
() => repository.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotosFromFixture);
|
||||
when(
|
||||
() => repository.hideConsultantCertPhoto(any<int>()),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await cubit.getMyRequestStatus('my-consultant-slug');
|
||||
// All photos from backend are present in state.
|
||||
expect(cubit.state.certPhotos, hasLength(3));
|
||||
|
||||
await cubit.removeCertPhoto(101, null);
|
||||
|
||||
// Current implementation only tracks deleted IDs; photos list remains unchanged here.
|
||||
verifyNever(() => repository.hideConsultantCertPhoto(any<int>()));
|
||||
expect(cubit.state.deletedPhotoIds, contains(101));
|
||||
expect(cubit.state.certPhotos, hasLength(3));
|
||||
@@ -136,7 +543,9 @@ void main() {
|
||||
test('uploadCertPhotos - не загружает если список пуст', () async {
|
||||
await cubit.addCertPhotos([]);
|
||||
|
||||
verifyNever(() => repository.storeConsultantCertPhotos(files: any(named: 'files')));
|
||||
verifyNever(
|
||||
() => repository.storeConsultantCertPhotos(files: any(named: 'files')),
|
||||
);
|
||||
});
|
||||
|
||||
test('updateConsultantProfileModel - обновляет без acceptOffer', () {
|
||||
@@ -147,4 +556,38 @@ void main() {
|
||||
expect(cubit.state.loading, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('AlfaScoringStatus', () {
|
||||
test('fromString maps known values', () {
|
||||
expect(
|
||||
AlfaScoringStatus.fromString('beneficiary_creating'),
|
||||
AlfaScoringStatus.beneficiaryCreating,
|
||||
);
|
||||
expect(
|
||||
AlfaScoringStatus.fromString('scoring_pending'),
|
||||
AlfaScoringStatus.scoringPending,
|
||||
);
|
||||
expect(
|
||||
AlfaScoringStatus.fromString('scoring_complete'),
|
||||
AlfaScoringStatus.scoringComplete,
|
||||
);
|
||||
expect(
|
||||
AlfaScoringStatus.fromString('scoring_failed'),
|
||||
AlfaScoringStatus.scoringFailed,
|
||||
);
|
||||
expect(
|
||||
AlfaScoringStatus.fromString('beneficiary_failed'),
|
||||
AlfaScoringStatus.beneficiaryFailed,
|
||||
);
|
||||
expect(AlfaScoringStatus.fromString(null), AlfaScoringStatus.none);
|
||||
expect(AlfaScoringStatus.fromString('unknown'), AlfaScoringStatus.none);
|
||||
});
|
||||
|
||||
test('helpers', () {
|
||||
expect(AlfaScoringStatus.scoringPending.isProcessing, isTrue);
|
||||
expect(AlfaScoringStatus.scoringFailed.canRetry, isTrue);
|
||||
expect(AlfaScoringStatus.scoringComplete.isComplete, isTrue);
|
||||
expect(AlfaScoringStatus.scoringComplete.isTerminal, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,15 +18,28 @@ void main() {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: Scaffold(
|
||||
body: BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: child),
|
||||
body: BlocProvider<RegisterConsultantCubit>.value(
|
||||
value: cubit,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders additional settings screen', (tester) async {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(hasMedicalEducation: null, socialLinks: null, photos: null, certificates: null);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: null,
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
|
||||
@@ -21,12 +21,18 @@ import 'certificates_and_diplomas_screen_smoke_test.mocks.dart';
|
||||
void main() {
|
||||
group('CertificatesAndDiplomasScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
dotenv.loadFromString(
|
||||
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
|
||||
);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
sl.registerLazySingleton<AppConfig>(
|
||||
() =>
|
||||
AppConfig(buildType: AppBuildType.tests)
|
||||
..setBaseUrl('http://localhost'),
|
||||
);
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
@@ -45,15 +51,30 @@ void main() {
|
||||
|
||||
testWidgets('renders certificates and diplomas screen', (tester) async {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(hasMedicalEducation: null, socialLinks: null, photos: null, certificates: null);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: null,
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateConsultantProfileModel(any)).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: wrap(const CertificatesAndDiplomasScreen())));
|
||||
await tester.pumpWidget(
|
||||
BlocProvider<RegisterConsultantCubit>.value(
|
||||
value: cubit,
|
||||
child: wrap(const CertificatesAndDiplomasScreen()),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
@@ -67,16 +88,28 @@ void main() {
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: null,
|
||||
photos: null,
|
||||
certificates: [CertificatesModel(title: 'Test Certificate', date: '2025-01-01')],
|
||||
certificates: [
|
||||
CertificatesModel(title: 'Test Certificate', date: '2025-01-01'),
|
||||
],
|
||||
);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateConsultantProfileModel(any)).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: wrap(const CertificatesAndDiplomasScreen())));
|
||||
await tester.pumpWidget(
|
||||
BlocProvider<RegisterConsultantCubit>.value(
|
||||
value: cubit,
|
||||
child: wrap(const CertificatesAndDiplomasScreen()),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
|
||||
@@ -19,43 +19,88 @@ import 'last_step_screen_test.mocks.dart';
|
||||
void main() {
|
||||
group('LastStepScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
dotenv.loadFromString(
|
||||
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
|
||||
);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
sl.registerLazySingleton<AppConfig>(
|
||||
() =>
|
||||
AppConfig(buildType: AppBuildType.tests)
|
||||
..setBaseUrl('http://localhost'),
|
||||
);
|
||||
});
|
||||
|
||||
Widget wrap(Widget child, RegisterConsultantCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: Scaffold(
|
||||
body: BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: child),
|
||||
body: BlocProvider<RegisterConsultantCubit>.value(
|
||||
value: cubit,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders last step screen', (tester) async {
|
||||
testWidgets(
|
||||
'renders last step screen with both agreements for new registration',
|
||||
(tester) async {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(wrap(const LastStepScreen(), cubit));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(LastStepScreen), findsOneWidget);
|
||||
expect(find.text('Последний шаг'), findsOneWidget);
|
||||
expect(find.byType(CheckboxListTile), findsNWidgets(2));
|
||||
expect(find.textContaining('ФРДО'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('hides FRDO checkbox in update mode', (tester) async {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
const consultant = ConsultantProfileModel();
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: true,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateConsultantProfileModel(any, any)).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(wrap(const LastStepScreen(), cubit));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(LastStepScreen), findsOneWidget);
|
||||
expect(find.text('Последний шаг'), findsOneWidget);
|
||||
expect(find.byType(CheckboxListTile), findsOneWidget);
|
||||
expect(find.textContaining('ФРДО'), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/passport_data_screen.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockConsultantRepository extends Mock implements ConsultantRepository {}
|
||||
|
||||
void main() {
|
||||
group('PassportDataScreen smoke', () {
|
||||
late RegisterConsultantCubit cubit;
|
||||
|
||||
setUp(() {
|
||||
cubit = RegisterConsultantCubit(
|
||||
consultantRepository: MockConsultantRepository(),
|
||||
);
|
||||
cubit.emit(
|
||||
const RegisterConsultantState(
|
||||
consultant: ConsultantProfileModel(socialLinks: SocialLinksModel()),
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: Scaffold(
|
||||
body: BlocProvider<RegisterConsultantCubit>.value(
|
||||
value: cubit,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders passport data fields', (tester) async {
|
||||
await tester.pumpWidget(wrap(const PassportDataScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(PassportDataScreen), findsOneWidget);
|
||||
expect(find.text('Паспортные данные'), findsOneWidget);
|
||||
expect(find.text('Фамилия'), findsOneWidget);
|
||||
expect(find.text('Имя'), findsOneWidget);
|
||||
expect(find.text('ИНН (12 цифр)'), findsOneWidget);
|
||||
expect(find.text('СНИЛС'), findsOneWidget);
|
||||
expect(find.text('Гражданство (ISO, например RU)'), findsOneWidget);
|
||||
expect(find.text('Телефон для выплат через СБП'), findsOneWidget);
|
||||
expect(find.text('Банк получатель (поиск)'), findsOneWidget);
|
||||
expect(find.text('Я являюсь налоговым резидентом США'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -19,12 +19,18 @@ import 'stepper_become_consultant_screen_smoke_test.mocks.dart';
|
||||
void main() {
|
||||
group('PhotoOrScansScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
dotenv.loadFromString(
|
||||
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
|
||||
);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
sl.registerLazySingleton<AppConfig>(
|
||||
() =>
|
||||
AppConfig(buildType: AppBuildType.tests)
|
||||
..setBaseUrl('http://localhost'),
|
||||
);
|
||||
});
|
||||
|
||||
Widget wrap(Widget child, RegisterConsultantCubit cubit) {
|
||||
@@ -38,7 +44,10 @@ void main() {
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Scaffold(
|
||||
body: BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: child),
|
||||
body: BlocProvider<RegisterConsultantCubit>.value(
|
||||
value: cubit,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -49,11 +58,22 @@ void main() {
|
||||
final cubit = RegisterConsultantCubit(consultantRepository: repository);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
cubit.emit(state);
|
||||
|
||||
@@ -61,7 +81,10 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(PhotoOrScansScreen), findsOneWidget);
|
||||
expect(find.text('Загрузите фото или сканы дипломов и сертификатов'), findsOneWidget);
|
||||
expect(
|
||||
find.text('Загрузите фото или сканы дипломов и сертификатов'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,11 +94,22 @@ void main() {
|
||||
final cubit = RegisterConsultantCubit(consultantRepository: repository);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
cubit.emit(state);
|
||||
|
||||
|
||||
@@ -1,24 +1,59 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockConsultantRepository extends Mock implements ConsultantRepository {}
|
||||
|
||||
void main() {
|
||||
group('RegistrationAsScreen smoke', () {
|
||||
late RegisterConsultantCubit cubit;
|
||||
|
||||
setUp(() {
|
||||
cubit = RegisterConsultantCubit(
|
||||
consultantRepository: MockConsultantRepository(),
|
||||
);
|
||||
cubit.emit(
|
||||
const RegisterConsultantState(
|
||||
consultant: ConsultantProfileModel(socialLinks: SocialLinksModel()),
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: Scaffold(body: child),
|
||||
home: Scaffold(
|
||||
body: BlocProvider<RegisterConsultantCubit>.value(
|
||||
value: cubit,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders registration as screen', (tester) async {
|
||||
await tester.pumpWidget(wrap(const RegistrationAsScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(RegistrationAsScreen), findsOneWidget);
|
||||
expect(find.text('Регистрация в качестве'), findsOneWidget);
|
||||
expect(find.text('Физического лица'), findsOneWidget);
|
||||
expect(cubit.state.consultant.legalType, 'individualPerson');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,12 +21,18 @@ import 'social_networks_screen_smoke_test.mocks.dart';
|
||||
void main() {
|
||||
group('SocialNetworksScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
dotenv.loadFromString(
|
||||
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
|
||||
);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
sl.registerLazySingleton<AppConfig>(
|
||||
() =>
|
||||
AppConfig(buildType: AppBuildType.tests)
|
||||
..setBaseUrl('http://localhost'),
|
||||
);
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
@@ -47,18 +53,34 @@ void main() {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateConsultantProfileModel(any)).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: wrap(const SocialNetworksScreen())));
|
||||
await tester.pumpWidget(
|
||||
BlocProvider<RegisterConsultantCubit>.value(
|
||||
value: cubit,
|
||||
child: wrap(const SocialNetworksScreen()),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
@@ -69,18 +91,34 @@ void main() {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: 'https://vk.com/test', tgLink: 'https://t.me/test', fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: 'https://vk.com/test',
|
||||
tgLink: 'https://t.me/test',
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateConsultantProfileModel(any)).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: wrap(const SocialNetworksScreen())));
|
||||
await tester.pumpWidget(
|
||||
BlocProvider<RegisterConsultantCubit>.value(
|
||||
value: cubit,
|
||||
child: wrap(const SocialNetworksScreen()),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
|
||||
@@ -21,12 +21,18 @@ import 'stepper_become_consultant_screen_smoke_test.mocks.dart';
|
||||
void main() {
|
||||
group('StepperBecomeConsultantScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
dotenv.loadFromString(
|
||||
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
|
||||
);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
sl.registerLazySingleton<AppConfig>(
|
||||
() =>
|
||||
AppConfig(buildType: AppBuildType.tests)
|
||||
..setBaseUrl('http://localhost'),
|
||||
);
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
@@ -48,11 +54,22 @@ void main() {
|
||||
final cubit = RegisterConsultantCubit(consultantRepository: repository);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
cubit.emit(state);
|
||||
|
||||
@@ -71,11 +88,22 @@ void main() {
|
||||
final cubit = RegisterConsultantCubit(consultantRepository: repository);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
cubit.emit(state);
|
||||
|
||||
@@ -86,7 +114,7 @@ void main() {
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('шаг 1 из 6'), findsOneWidget);
|
||||
expect(find.text('шаг 1 из 7'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('displays progress bar', (tester) async {
|
||||
@@ -94,11 +122,22 @@ void main() {
|
||||
final cubit = RegisterConsultantCubit(consultantRepository: repository);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
cubit.emit(state);
|
||||
|
||||
@@ -117,11 +156,22 @@ void main() {
|
||||
final cubit = RegisterConsultantCubit(consultantRepository: repository);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
cubit.emit(state);
|
||||
|
||||
@@ -140,11 +190,22 @@ void main() {
|
||||
final cubit = RegisterConsultantCubit(consultantRepository: repository);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
cubit.emit(state);
|
||||
|
||||
@@ -163,11 +224,22 @@ void main() {
|
||||
final cubit = RegisterConsultantCubit(consultantRepository: repository);
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
socialLinks: SocialLinksModel(
|
||||
vkLink: null,
|
||||
tgLink: null,
|
||||
fbLink: null,
|
||||
instaLink: null,
|
||||
personalSiteLink: null,
|
||||
),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
|
||||
const state = RegisterConsultantState(
|
||||
consultant: consultant,
|
||||
acceptOffer: false,
|
||||
loading: false,
|
||||
isUpdating: false,
|
||||
);
|
||||
|
||||
cubit.emit(state);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/sbp_bank_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -36,42 +37,99 @@ void main() {
|
||||
setUp(() {
|
||||
mockService = MockConsultantService();
|
||||
repository = ConsultantRepository(consultantService: mockService);
|
||||
consultantProfile = ConsultantProfileModel.fromJson(FixtureReader.readAsMap('consultant/consultant_profile_request.json'));
|
||||
consultationRequest = ConsultationRequestModel.fromJson(FixtureReader.readAsMap('consultant/consultation_request.json'));
|
||||
certPhotos = FixtureReader.readAsList('consultant/consultant_cert_photos.json')
|
||||
.map((item) => ConsultantCertPhotoModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList();
|
||||
consultantProfile = ConsultantProfileModel.fromJson(
|
||||
FixtureReader.readAsMap('consultant/consultant_profile_request.json'),
|
||||
);
|
||||
consultationRequest = ConsultationRequestModel.fromJson(
|
||||
FixtureReader.readAsMap('consultant/consultation_request.json'),
|
||||
);
|
||||
certPhotos =
|
||||
FixtureReader.readAsList('consultant/consultant_cert_photos.json')
|
||||
.map(
|
||||
(item) => ConsultantCertPhotoModel.fromJson(
|
||||
item as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
});
|
||||
|
||||
test('registerConsultant delegates to consultantRegistration when update=false', () async {
|
||||
when(() => mockService.consultantRegistration(consultantModel: any<ConsultantProfileModel>(named: 'consultantModel'))).thenAnswer((_) async {});
|
||||
test(
|
||||
'registerConsultant delegates to consultantRegistration when update=false',
|
||||
() async {
|
||||
when(
|
||||
() => mockService.consultantRegistration(
|
||||
consultantModel: any<ConsultantProfileModel>(
|
||||
named: 'consultantModel',
|
||||
),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await repository.registerConsultant(consultant: consultantProfile);
|
||||
await repository.registerConsultant(consultant: consultantProfile);
|
||||
|
||||
verify(() => mockService.consultantRegistration(consultantModel: consultantProfile)).called(1);
|
||||
verifyNever(() => mockService.consultantUpdate(consultantModel: any<ConsultantProfileModel>(named: 'consultantModel')));
|
||||
});
|
||||
verify(
|
||||
() => mockService.consultantRegistration(
|
||||
consultantModel: consultantProfile,
|
||||
),
|
||||
).called(1);
|
||||
verifyNever(
|
||||
() => mockService.consultantUpdate(
|
||||
consultantModel: any<ConsultantProfileModel>(
|
||||
named: 'consultantModel',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('registerConsultant delegates to consultantUpdate when update=true', () async {
|
||||
when(() => mockService.consultantUpdate(consultantModel: any<ConsultantProfileModel>(named: 'consultantModel'))).thenAnswer((_) async {});
|
||||
test(
|
||||
'registerConsultant delegates to consultantUpdate when update=true',
|
||||
() async {
|
||||
when(
|
||||
() => mockService.consultantUpdate(
|
||||
consultantModel: any<ConsultantProfileModel>(
|
||||
named: 'consultantModel',
|
||||
),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await repository.registerConsultant(consultant: consultantProfile, update: true);
|
||||
await repository.registerConsultant(
|
||||
consultant: consultantProfile,
|
||||
update: true,
|
||||
);
|
||||
|
||||
verify(() => mockService.consultantUpdate(consultantModel: consultantProfile)).called(1);
|
||||
verifyNever(() => mockService.consultantRegistration(consultantModel: any<ConsultantProfileModel>(named: 'consultantModel')));
|
||||
});
|
||||
verify(
|
||||
() =>
|
||||
mockService.consultantUpdate(consultantModel: consultantProfile),
|
||||
).called(1);
|
||||
verifyNever(
|
||||
() => mockService.consultantRegistration(
|
||||
consultantModel: any<ConsultantProfileModel>(
|
||||
named: 'consultantModel',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('getConsultantRequestStatus returns request from service', () async {
|
||||
when(() => mockService.getConsultantRequestStatus(slug: any<String>(named: 'slug'))).thenAnswer((_) async => consultationRequest);
|
||||
when(
|
||||
() => mockService.getConsultantRequestStatus(
|
||||
slug: any<String>(named: 'slug'),
|
||||
),
|
||||
).thenAnswer((_) async => consultationRequest);
|
||||
|
||||
final result = await repository.getConsultantRequestStatus('user-slug');
|
||||
|
||||
expect(result, equals(consultationRequest));
|
||||
verify(() => mockService.getConsultantRequestStatus(slug: 'user-slug')).called(1);
|
||||
verify(
|
||||
() => mockService.getConsultantRequestStatus(slug: 'user-slug'),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('getMyRequestStatus returns request from service', () async {
|
||||
when(() => mockService.getMyRequestStatus(slug: any<String>(named: 'slug'))).thenAnswer((_) async => consultationRequest);
|
||||
when(
|
||||
() => mockService.getMyRequestStatus(slug: any<String>(named: 'slug')),
|
||||
).thenAnswer((_) async => consultationRequest);
|
||||
|
||||
final result = await repository.getMyRequestStatus('my-slug');
|
||||
|
||||
@@ -80,7 +138,9 @@ void main() {
|
||||
});
|
||||
|
||||
test('getConsultantCertPhotos returns photos from service', () async {
|
||||
when(() => mockService.getConsultantCertPhotos()).thenAnswer((_) async => certPhotos);
|
||||
when(
|
||||
() => mockService.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => certPhotos);
|
||||
|
||||
final result = await repository.getConsultantCertPhotos();
|
||||
|
||||
@@ -88,42 +148,92 @@ void main() {
|
||||
verify(() => mockService.getConsultantCertPhotos()).called(1);
|
||||
});
|
||||
|
||||
test('storeConsultantCertPhotos returns empty list when no files provided', () async {
|
||||
final result = await repository.storeConsultantCertPhotos(files: []);
|
||||
test('getSbpBanks returns banks from service', () async {
|
||||
final banks = FixtureReader.readAsList('consultant/sbp_banks.json')
|
||||
.map((item) => SbpBankModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList();
|
||||
when(() => mockService.getSbpBanks()).thenAnswer((_) async => banks);
|
||||
|
||||
expect(result, isEmpty);
|
||||
verifyNever(() => mockService.storeConsultantCertPhotos(photos: any<List<MultipartFile>>(named: 'photos')));
|
||||
final result = await repository.getSbpBanks();
|
||||
|
||||
expect(result, banks);
|
||||
verify(() => mockService.getSbpBanks()).called(1);
|
||||
});
|
||||
|
||||
test('storeConsultantCertPhotos uploads prepared files and returns response', () async {
|
||||
final mockFile = MockFile();
|
||||
when(() => mockFile.readAsBytes()).thenAnswer((_) async => Uint8List.fromList([1, 2, 3]));
|
||||
when(() => mockFile.path).thenReturn('/tmp/certificate.png');
|
||||
when(() => mockService.storeConsultantCertPhotos(photos: any<List<MultipartFile>>(named: 'photos'))).thenAnswer((_) async => certPhotos);
|
||||
test(
|
||||
'storeConsultantCertPhotos returns empty list when no files provided',
|
||||
() async {
|
||||
final result = await repository.storeConsultantCertPhotos(files: []);
|
||||
|
||||
final result = await repository.storeConsultantCertPhotos(files: [mockFile]);
|
||||
expect(result, isEmpty);
|
||||
verifyNever(
|
||||
() => mockService.storeConsultantCertPhotos(
|
||||
photos: any<List<MultipartFile>>(named: 'photos'),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
expect(result, certPhotos);
|
||||
final capturedArgument = verify(() => mockService.storeConsultantCertPhotos(photos: captureAny<List<MultipartFile>>(named: 'photos'))).captured.single as List<MultipartFile>;
|
||||
expect(capturedArgument, hasLength(1));
|
||||
expect(capturedArgument.first.filename, 'certificate.png');
|
||||
});
|
||||
test(
|
||||
'storeConsultantCertPhotos uploads prepared files and returns response',
|
||||
() async {
|
||||
final mockFile = MockFile();
|
||||
when(
|
||||
() => mockFile.readAsBytes(),
|
||||
).thenAnswer((_) async => Uint8List.fromList([1, 2, 3]));
|
||||
when(() => mockFile.path).thenReturn('/tmp/certificate.png');
|
||||
when(
|
||||
() => mockService.storeConsultantCertPhotos(
|
||||
photos: any<List<MultipartFile>>(named: 'photos'),
|
||||
),
|
||||
).thenAnswer((_) async => certPhotos);
|
||||
|
||||
final result = await repository.storeConsultantCertPhotos(
|
||||
files: [mockFile],
|
||||
);
|
||||
|
||||
expect(result, certPhotos);
|
||||
final capturedArgument =
|
||||
verify(
|
||||
() => mockService.storeConsultantCertPhotos(
|
||||
photos: captureAny<List<MultipartFile>>(named: 'photos'),
|
||||
),
|
||||
).captured.single
|
||||
as List<MultipartFile>;
|
||||
expect(capturedArgument, hasLength(1));
|
||||
expect(capturedArgument.first.filename, 'certificate.png');
|
||||
},
|
||||
);
|
||||
|
||||
test('hideConsultantCertPhoto sends payload to service', () async {
|
||||
when(() => mockService.hideConsultantCertPhoto(payload: any<Map<String, dynamic>>(named: 'payload'))).thenAnswer((_) async {});
|
||||
when(
|
||||
() => mockService.hideConsultantCertPhoto(
|
||||
payload: any<Map<String, dynamic>>(named: 'payload'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await repository.hideConsultantCertPhoto(42);
|
||||
|
||||
verify(() => mockService.hideConsultantCertPhoto(payload: {'cert_photo_id': 42})).called(1);
|
||||
verify(
|
||||
() =>
|
||||
mockService.hideConsultantCertPhoto(payload: {'cert_photo_id': 42}),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('unHideConsultantCertPhoto sends payload to service', () async {
|
||||
when(() => mockService.unHideConsultantCertPhoto(payload: any<Map<String, dynamic>>(named: 'payload'))).thenAnswer((_) async {});
|
||||
when(
|
||||
() => mockService.unHideConsultantCertPhoto(
|
||||
payload: any<Map<String, dynamic>>(named: 'payload'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await repository.unHideConsultantCertPhoto(7);
|
||||
|
||||
verify(() => mockService.unHideConsultantCertPhoto(payload: {'cert_photo_id': 7})).called(1);
|
||||
verify(
|
||||
() => mockService.unHideConsultantCertPhoto(
|
||||
payload: {'cert_photo_id': 7},
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,14 @@ void main() {
|
||||
|
||||
test('getMyRequestStatus - возвращает статус запроса', () async {
|
||||
const slug = 'test-consultant';
|
||||
final json = FixtureReader.readAsMap('consultant/consultation_request.json');
|
||||
final json = FixtureReader.readAsMap(
|
||||
'consultant/consultation_request.json',
|
||||
);
|
||||
final expectedRequest = ConsultationRequestModel.fromJson(json);
|
||||
|
||||
when(mockConsultantService.getMyRequestStatus(slug: slug))
|
||||
.thenAnswer((_) async => expectedRequest);
|
||||
when(
|
||||
mockConsultantService.getMyRequestStatus(slug: slug),
|
||||
).thenAnswer((_) async => expectedRequest);
|
||||
|
||||
final result = await mockConsultantService.getMyRequestStatus(slug: slug);
|
||||
|
||||
@@ -33,25 +36,36 @@ void main() {
|
||||
verify(mockConsultantService.getMyRequestStatus(slug: slug)).called(1);
|
||||
});
|
||||
|
||||
test('getConsultantRequestStatus - возвращает статус запроса консультанта', () async {
|
||||
const slug = 'test-consultant';
|
||||
final json = FixtureReader.readAsMap('consultant/consultation_request.json');
|
||||
final expectedRequest = ConsultationRequestModel.fromJson(json);
|
||||
test(
|
||||
'getConsultantRequestStatus - возвращает статус запроса консультанта',
|
||||
() async {
|
||||
const slug = 'test-consultant';
|
||||
final json = FixtureReader.readAsMap(
|
||||
'consultant/consultation_request.json',
|
||||
);
|
||||
final expectedRequest = ConsultationRequestModel.fromJson(json);
|
||||
|
||||
when(mockConsultantService.getConsultantRequestStatus(slug: slug))
|
||||
.thenAnswer((_) async => expectedRequest);
|
||||
when(
|
||||
mockConsultantService.getConsultantRequestStatus(slug: slug),
|
||||
).thenAnswer((_) async => expectedRequest);
|
||||
|
||||
final result = await mockConsultantService.getConsultantRequestStatus(slug: slug);
|
||||
final result = await mockConsultantService.getConsultantRequestStatus(
|
||||
slug: slug,
|
||||
);
|
||||
|
||||
expect(result, isA<ConsultationRequestModel>());
|
||||
verify(mockConsultantService.getConsultantRequestStatus(slug: slug)).called(1);
|
||||
});
|
||||
expect(result, isA<ConsultationRequestModel>());
|
||||
verify(
|
||||
mockConsultantService.getConsultantRequestStatus(slug: slug),
|
||||
).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
test('getConsultantCertPhotos - возвращает фото сертификатов', () async {
|
||||
final expectedPhotos = <ConsultantCertPhotoModel>[];
|
||||
|
||||
when(mockConsultantService.getConsultantCertPhotos())
|
||||
.thenAnswer((_) async => expectedPhotos);
|
||||
when(
|
||||
mockConsultantService.getConsultantCertPhotos(),
|
||||
).thenAnswer((_) async => expectedPhotos);
|
||||
|
||||
final result = await mockConsultantService.getConsultantCertPhotos();
|
||||
|
||||
@@ -60,16 +74,22 @@ void main() {
|
||||
});
|
||||
|
||||
test('consultantRegistration - регистрирует консультанта', () async {
|
||||
final profileJson = FixtureReader.readAsMap('consultant/consultant_profile_request.json');
|
||||
final profileJson = FixtureReader.readAsMap(
|
||||
'consultant/consultant_profile_request.json',
|
||||
);
|
||||
final profile = ConsultantProfileModel.fromJson(profileJson);
|
||||
|
||||
when(mockConsultantService.consultantRegistration(consultantModel: profile))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
when(
|
||||
mockConsultantService.consultantRegistration(consultantModel: profile),
|
||||
).thenAnswer((_) async => Future.value());
|
||||
|
||||
await mockConsultantService.consultantRegistration(consultantModel: profile);
|
||||
await mockConsultantService.consultantRegistration(
|
||||
consultantModel: profile,
|
||||
);
|
||||
|
||||
verify(mockConsultantService.consultantRegistration(consultantModel: profile)).called(1);
|
||||
verify(
|
||||
mockConsultantService.consultantRegistration(consultantModel: profile),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/utils/laravel_validation_errors.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('parseLaravelValidationErrors', () {
|
||||
DioException dio422(Map<String, dynamic> data) {
|
||||
return DioException(
|
||||
requestOptions: RequestOptions(path: '/test'),
|
||||
response: Response(
|
||||
requestOptions: RequestOptions(path: '/test'),
|
||||
statusCode: 422,
|
||||
data: data,
|
||||
),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
}
|
||||
|
||||
test('maps unique and format errors to Russian UI texts', () {
|
||||
final parsed = parseLaravelValidationErrors(
|
||||
dio422({
|
||||
'message': 'The given data was invalid.',
|
||||
'errors': {
|
||||
'inn': ['The inn format is invalid.'],
|
||||
'passport_number': ['The passport number has already been taken.'],
|
||||
'snils': ['The snils has already been taken.'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(parsed.fieldErrors['inn'], 'Некорректный ИНН');
|
||||
expect(parsed.fieldErrors['passport_number'], 'Такой паспорт существует');
|
||||
expect(parsed.fieldErrors['snils'], 'Такой СНИЛС существует');
|
||||
expect(parsed.summary, isNot(contains('The given data was invalid.')));
|
||||
expect(parsed.summary, isNot(contains('already been taken')));
|
||||
expect(parsed.summary, contains('Некорректный ИНН'));
|
||||
expect(parsed.summary, contains('Такой паспорт существует'));
|
||||
expect(parsed.hasFieldErrors, isTrue);
|
||||
});
|
||||
|
||||
test('maps required and agreement errors', () {
|
||||
final parsed = parseLaravelValidationErrors(
|
||||
dio422({
|
||||
'errors': {
|
||||
'last_name': ['The last name field is required.'],
|
||||
'agreement_accepted': ['The agreement accepted must be accepted.'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(parsed.fieldErrors['last_name'], 'Укажите фамилию');
|
||||
expect(parsed.fieldErrors['agreement_accepted'], 'Примите условия оферты');
|
||||
});
|
||||
|
||||
test('falls back to generic message for non-422 DioException', () {
|
||||
final error = DioException(
|
||||
requestOptions: RequestOptions(path: '/test'),
|
||||
message: 'Network failed',
|
||||
type: DioExceptionType.connectionError,
|
||||
);
|
||||
|
||||
final parsed = parseLaravelValidationErrors(error);
|
||||
|
||||
expect(parsed.summary, 'Не удалось отправить данные. Попробуйте ещё раз');
|
||||
expect(parsed.fieldErrors, isEmpty);
|
||||
});
|
||||
|
||||
test('uses generic text for non-Dio errors', () {
|
||||
final parsed = parseLaravelValidationErrors(Exception('boom'));
|
||||
|
||||
expect(parsed.summary, 'Не удалось отправить данные. Попробуйте ещё раз');
|
||||
expect(parsed.fieldErrors, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('mapConsultantValidationMessage', () {
|
||||
test('unique inn and snils', () {
|
||||
expect(
|
||||
mapConsultantValidationMessage(
|
||||
fieldKey: 'inn',
|
||||
rawMessage: 'The inn has already been taken.',
|
||||
),
|
||||
'Такой ИНН существует',
|
||||
);
|
||||
expect(
|
||||
mapConsultantValidationMessage(
|
||||
fieldKey: 'snils',
|
||||
rawMessage: 'The snils has already been taken.',
|
||||
),
|
||||
'Такой СНИЛС существует',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -20,6 +20,26 @@
|
||||
"title": "Психология питания",
|
||||
"date": "2023-06-05"
|
||||
}
|
||||
]
|
||||
],
|
||||
"legal_type": "individualPerson",
|
||||
"last_name": "Иванов",
|
||||
"first_name": "Иван",
|
||||
"middle_name": "Иванович",
|
||||
"inn": "123456789012",
|
||||
"snils": "123-456-789 00",
|
||||
"passport_series": "1234",
|
||||
"passport_number": "567890",
|
||||
"passport_issue_date": "2020-01-15",
|
||||
"passport_issuer": "ОВД Пресненского района г. Москвы",
|
||||
"passport_issuer_code": "772-053",
|
||||
"birth_date": "1990-05-20",
|
||||
"birth_place": "г. Москва",
|
||||
"registration_address": "г. Москва, ул. Тверская, д. 1, кв. 1",
|
||||
"residence_address": "г. Москва, ул. Тверская, д. 1, кв. 1",
|
||||
"citizenship": "RU",
|
||||
"tax_resident_us": false,
|
||||
"foreign_tax_resident": false,
|
||||
"sbp_phone": "+79991234567",
|
||||
"agreement_accepted": true,
|
||||
"frdo_agreement_accepted": true
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,31 @@
|
||||
"personal_site_link": "https://consultant.eatvio.app",
|
||||
"request_state": "approved"
|
||||
},
|
||||
"legal_entity_resource": {
|
||||
"id": 1,
|
||||
"type": "individualPerson",
|
||||
"last_name": "Иванов",
|
||||
"first_name": "Иван",
|
||||
"middle_name": "Иванович",
|
||||
"inn": "123456789012",
|
||||
"snils": "123-456-789 00",
|
||||
"state": "created",
|
||||
"alfa_scoring_status": "scoring_failed",
|
||||
"alfa_scoring_request_id": "abc-123",
|
||||
"passport_series": "1234",
|
||||
"passport_number": "567890",
|
||||
"passport_issue_date": "2020-01-15",
|
||||
"passport_issuer": "ОВД Пресненского района г. Москвы",
|
||||
"passport_issuer_code": "772-053",
|
||||
"birth_date": "1990-05-20",
|
||||
"birth_place": "г. Москва",
|
||||
"registration_address": "г. Москва, ул. Тверская, д. 1, кв. 1",
|
||||
"residence_address": "г. Москва, ул. Тверская, д. 1, кв. 1",
|
||||
"citizenship": "RU",
|
||||
"tax_resident_us": false,
|
||||
"foreign_tax_resident": false,
|
||||
"sbp_phone": "+79991234567"
|
||||
},
|
||||
"cert_photos": [
|
||||
{
|
||||
"url": "https://cdn.eatvio.app/certificates/visible.jpg"
|
||||
@@ -40,4 +65,3 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
test/fixtures/consultant/sbp_banks.json
vendored
Normal file
16
test/fixtures/consultant/sbp_banks.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"bank_member_id": "100000000111",
|
||||
"bank_name": "Сбербанк",
|
||||
"short_name": "Сбер",
|
||||
"logo_url": "https://example.com/sber.png",
|
||||
"bic": "044525225"
|
||||
},
|
||||
{
|
||||
"bank_member_id": "100000000004",
|
||||
"bank_name": "Т-Банк",
|
||||
"short_name": "ТБанк",
|
||||
"logo_url": "https://example.com/tbank.png",
|
||||
"bic": "044525974"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user