import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tm_app/core/network/app_exceptions.dart'; import 'package:tm_app/core/network/network_manager.dart'; import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/data/repositories/company_ai_repository.dart'; import 'package:tm_app/data/repositories/home_repository.dart'; import 'package:tm_app/data/services/company_ai_service.dart'; import 'package:tm_app/data/services/home_service.dart'; import 'package:tm_app/data/services/liked_tenders_service.dart'; import 'package:tm_app/data/services/model/recommend_response/recommend_response.dart'; import 'package:tm_app/data/services/model/recommendation_item/recommendation_item.dart'; import 'package:tm_app/data/services/model/recommended_tenders_data/recommended_tenders_data.dart'; import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/data/services/your_tenders_service.dart'; import 'package:tm_app/view_models/ai_recommendations_view_model.dart'; void main() { group('AiRecommendationsViewModel', () { test('falls back to legacy recommendations on HTTP 503', () async { final prefs = await _prefs(); final companyRepository = _FakeCompanyAiRepository( prefs, results: [Result.error(ServerException('unavailable', 503))], ); final homeRepository = _FakeHomeRepository( prefs, result: const Result.ok( RecommendedTendersResponse( succes: true, message: 'ok', error: null, data: RecommendedTendersData(tenders: [], metadata: null), ), ), ); final viewModel = AiRecommendationsViewModel( companyAiRepository: companyRepository, homeRepository: homeRepository, ); await viewModel.loadRecommendations(); expect(viewModel.hasLoaded, isTrue); expect(viewModel.usingFallback, isTrue); expect(viewModel.errorMessage, isNull); expect(homeRepository.recommendedTendersCalls, 1); }); test('keeps failed transient loads retryable for loadIfNeeded', () async { final prefs = await _prefs(); final companyRepository = _FakeCompanyAiRepository( prefs, results: [ Result.error(FetchDataException('network failed')), const Result.ok( RecommendResponse( data: [ RecommendationItem( rank: '1', tenderId: 'tender-1', analysis: 'Strong match', ), ], error: null, message: 'ok', success: true, ), ), ], ); final viewModel = AiRecommendationsViewModel( companyAiRepository: companyRepository, homeRepository: _FakeHomeRepository(prefs), ); await viewModel.loadRecommendations(); await viewModel.loadIfNeeded(); expect(companyRepository.recommendationCalls, 2); expect(viewModel.hasLoaded, isTrue); expect(viewModel.errorMessage, isNull); expect(viewModel.items.single.tenderId, 'tender-1'); }); test( 'marks empty results as still learning after recent onboarding', () async { final prefs = await _prefs(); final viewModel = AiRecommendationsViewModel( companyAiRepository: _FakeCompanyAiRepository( prefs, lastOnboardingStartedAt: DateTime.now().millisecondsSinceEpoch, results: [ const Result.ok( RecommendResponse( data: [], error: null, message: 'ok', success: true, ), ), ], ), homeRepository: _FakeHomeRepository(prefs), ); await viewModel.loadRecommendations(); expect(viewModel.hasLoaded, isTrue); expect(viewModel.stillLearning, isTrue); expect(viewModel.isEmpty, isTrue); }, ); }); } Future _prefs() async { SharedPreferences.setMockInitialValues({}); return SharedPreferences.getInstance(); } class _FakeCompanyAiRepository extends CompanyAiRepository { _FakeCompanyAiRepository( SharedPreferences prefs, { required List> results, int? lastOnboardingStartedAt, }) : _results = results, _lastOnboardingStartedAt = lastOnboardingStartedAt, super(companyAiService: _UnusedCompanyAiService(prefs), prefs: prefs); final List> _results; final int? _lastOnboardingStartedAt; int recommendationCalls = 0; @override Future> getRecommendations() async { final index = recommendationCalls; recommendationCalls += 1; return _results[index]; } @override int? lastOnboardingStartedAt() => _lastOnboardingStartedAt; } class _FakeHomeRepository extends HomeRepository { _FakeHomeRepository( SharedPreferences prefs, { this.result = const Result.ok( RecommendedTendersResponse( succes: true, message: 'ok', error: null, data: RecommendedTendersData(tenders: [], metadata: null), ), ), }) : super( homeService: HomeService(networkManager: NetworkManager(prefs)), yourTendersService: YourTendersService( networkManager: NetworkManager(prefs), ), likedTendersService: LikedTendersService( networkManager: NetworkManager(prefs), ), ); final Result result; int recommendedTendersCalls = 0; @override Future> getHomeRecommendedTenders({ required int limit, required int offset, }) async { recommendedTendersCalls += 1; return result; } } class _UnusedCompanyAiService extends CompanyAiService { _UnusedCompanyAiService(SharedPreferences prefs) : super(networkManager: NetworkManager(prefs)); }