|
| 1 | +import 'package:bb_mobile/core/recoverbull/domain/usecases/fetch_recoverbull_url_usecase.dart'; |
| 2 | +import 'package:bb_mobile/core/recoverbull/domain/usecases/store_recoverbull_url_usecase.dart'; |
| 3 | +import 'package:bb_mobile/core/utils/constants.dart'; |
| 4 | +import 'package:bb_mobile/features/recoverbull/ui/pages/settings_page.dart'; |
| 5 | +import 'package:bb_mobile/generated/l10n/localization.dart'; |
| 6 | +import 'package:bb_mobile/locator.dart'; |
| 7 | +import 'package:flutter/material.dart'; |
| 8 | +import 'package:flutter_test/flutter_test.dart'; |
| 9 | +import 'package:go_router/go_router.dart'; |
| 10 | +import 'package:mocktail/mocktail.dart'; |
| 11 | + |
| 12 | +class _MockFetchUrlUsecase extends Mock implements FetchRecoverbullUrlUsecase {} |
| 13 | + |
| 14 | +class _MockStoreUrlUsecase extends Mock implements StoreRecoverbullUrlUsecase {} |
| 15 | + |
| 16 | +void main() { |
| 17 | + const defaultUrl = SettingsConstants.recoverbullUrl; |
| 18 | + const customUrl = |
| 19 | + 'http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.onion'; |
| 20 | + const otherCustomUrl = |
| 21 | + 'http://bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.onion'; |
| 22 | + |
| 23 | + late AppLocalizations loc; |
| 24 | + late _MockFetchUrlUsecase fetchUrl; |
| 25 | + late _MockStoreUrlUsecase storeUrl; |
| 26 | + |
| 27 | + setUpAll(() async { |
| 28 | + loc = await AppLocalizations.delegate.load(const Locale('en')); |
| 29 | + registerFallbackValue(Uri.parse(defaultUrl)); |
| 30 | + }); |
| 31 | + |
| 32 | + setUp(() { |
| 33 | + fetchUrl = _MockFetchUrlUsecase(); |
| 34 | + storeUrl = _MockStoreUrlUsecase(); |
| 35 | + when(() => storeUrl.execute(any())).thenAnswer((_) async {}); |
| 36 | + locator |
| 37 | + ..registerFactory<FetchRecoverbullUrlUsecase>(() => fetchUrl) |
| 38 | + ..registerFactory<StoreRecoverbullUrlUsecase>(() => storeUrl); |
| 39 | + }); |
| 40 | + |
| 41 | + tearDown(() async => locator.reset()); |
| 42 | + |
| 43 | + /// Opens the page with [current] as the stored server and types [entered] |
| 44 | + /// into the URL field, then presses Save. |
| 45 | + Future<void> editAndSave( |
| 46 | + WidgetTester tester, { |
| 47 | + required String current, |
| 48 | + required String entered, |
| 49 | + }) async { |
| 50 | + when(() => fetchUrl.execute()).thenAnswer((_) async => Uri.parse(current)); |
| 51 | + |
| 52 | + await tester.pumpWidget( |
| 53 | + MaterialApp.router( |
| 54 | + localizationsDelegates: AppLocalizations.localizationsDelegates, |
| 55 | + supportedLocales: AppLocalizations.supportedLocales, |
| 56 | + routerConfig: GoRouter( |
| 57 | + routes: [GoRoute(path: '/', builder: (_, _) => const SettingsPage())], |
| 58 | + ), |
| 59 | + ), |
| 60 | + ); |
| 61 | + await tester.pumpAndSettle(); |
| 62 | + |
| 63 | + await tester.tap(find.text(loc.recoverbullSettingsEdit)); |
| 64 | + await tester.pumpAndSettle(); |
| 65 | + await tester.enterText(find.byType(TextFormField), entered); |
| 66 | + await tester.tap(find.text(loc.recoverbullSettingsSave)); |
| 67 | + await tester.pumpAndSettle(); |
| 68 | + } |
| 69 | + |
| 70 | + testWidgets('a new custom server is only stored once the warning is ' |
| 71 | + 'confirmed', (tester) async { |
| 72 | + await editAndSave(tester, current: defaultUrl, entered: customUrl); |
| 73 | + |
| 74 | + expect(find.text(loc.securityWarningTitle), findsOneWidget); |
| 75 | + expect(find.text(loc.recoverbullServerCustomWarning), findsOneWidget); |
| 76 | + verifyNever(() => storeUrl.execute(any())); |
| 77 | + |
| 78 | + await tester.tap(find.text(loc.recoverbullContinue)); |
| 79 | + await tester.pumpAndSettle(); |
| 80 | + |
| 81 | + verify(() => storeUrl.execute(Uri.parse(customUrl))).called(1); |
| 82 | + }); |
| 83 | + |
| 84 | + testWidgets('dismissing the warning stores nothing and keeps the server in ' |
| 85 | + 'use', (tester) async { |
| 86 | + await editAndSave(tester, current: defaultUrl, entered: customUrl); |
| 87 | + expect(find.text(loc.securityWarningTitle), findsOneWidget); |
| 88 | + |
| 89 | + // Tap the barrier: the same escape a user gets from any bottom sheet. |
| 90 | + await tester.tapAt(const Offset(10, 10)); |
| 91 | + await tester.pumpAndSettle(); |
| 92 | + |
| 93 | + verifyNever(() => storeUrl.execute(any())); |
| 94 | + expect(find.text(loc.securityWarningTitle), findsNothing); |
| 95 | + // Back out of editing, showing the server that is actually in use. |
| 96 | + expect(find.text(defaultUrl), findsOneWidget); |
| 97 | + expect(find.text(customUrl), findsNothing); |
| 98 | + }); |
| 99 | + |
| 100 | + testWidgets('returning to the default server needs no warning', ( |
| 101 | + tester, |
| 102 | + ) async { |
| 103 | + await editAndSave(tester, current: customUrl, entered: defaultUrl); |
| 104 | + |
| 105 | + expect(find.text(loc.securityWarningTitle), findsNothing); |
| 106 | + verify(() => storeUrl.execute(Uri.parse(defaultUrl))).called(1); |
| 107 | + }); |
| 108 | + |
| 109 | + testWidgets('re-saving the server already in use does not re-prompt', ( |
| 110 | + tester, |
| 111 | + ) async { |
| 112 | + await editAndSave(tester, current: customUrl, entered: customUrl); |
| 113 | + |
| 114 | + expect(find.text(loc.securityWarningTitle), findsNothing); |
| 115 | + verify(() => storeUrl.execute(Uri.parse(customUrl))).called(1); |
| 116 | + }); |
| 117 | + |
| 118 | + testWidgets('swapping one custom server for another still warns', ( |
| 119 | + tester, |
| 120 | + ) async { |
| 121 | + await editAndSave(tester, current: customUrl, entered: otherCustomUrl); |
| 122 | + |
| 123 | + expect(find.text(loc.securityWarningTitle), findsOneWidget); |
| 124 | + verifyNever(() => storeUrl.execute(any())); |
| 125 | + }); |
| 126 | + |
| 127 | + testWidgets('an invalid URL is rejected before the warning appears', ( |
| 128 | + tester, |
| 129 | + ) async { |
| 130 | + await editAndSave(tester, current: defaultUrl, entered: 'https://evil.com'); |
| 131 | + |
| 132 | + expect(find.text(loc.recoverbullSettingsUrlMustBeHttp), findsOneWidget); |
| 133 | + expect(find.text(loc.securityWarningTitle), findsNothing); |
| 134 | + verifyNever(() => storeUrl.execute(any())); |
| 135 | + }); |
| 136 | +} |
0 commit comments