at main 5.9 kB view raw
1import 'package:coves_flutter/utils/url_launcher.dart'; 2import 'package:flutter/material.dart'; 3import 'package:flutter_test/flutter_test.dart'; 4import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; 5 6import '../test_helpers/mock_url_launcher_platform.dart'; 7 8void main() { 9 TestWidgetsFlutterBinding.ensureInitialized(); 10 11 late MockUrlLauncherPlatform mockPlatform; 12 13 setUp(() { 14 mockPlatform = MockUrlLauncherPlatform(); 15 UrlLauncherPlatform.instance = mockPlatform; 16 }); 17 18 group('UrlLauncher', () { 19 group('Security Validation', () { 20 test('blocks javascript: scheme', () async { 21 final result = await UrlLauncher.launchExternalUrl( 22 'javascript:alert("xss")', 23 ); 24 expect(result, false); 25 expect(mockPlatform.launchedUrls, isEmpty); 26 }); 27 28 test('blocks file: scheme', () async { 29 final result = await UrlLauncher.launchExternalUrl( 30 'file:///etc/passwd', 31 ); 32 expect(result, false); 33 expect(mockPlatform.launchedUrls, isEmpty); 34 }); 35 36 test('blocks data: scheme', () async { 37 final result = await UrlLauncher.launchExternalUrl( 38 'data:text/html,<h1>XSS</h1>', 39 ); 40 expect(result, false); 41 expect(mockPlatform.launchedUrls, isEmpty); 42 }); 43 44 test('allows http scheme', () async { 45 final result = await UrlLauncher.launchExternalUrl( 46 'http://example.com', 47 ); 48 expect(result, true); 49 expect(mockPlatform.launchedUrls, contains('http://example.com')); 50 }); 51 52 test('allows https scheme', () async { 53 final result = await UrlLauncher.launchExternalUrl( 54 'https://example.com', 55 ); 56 expect(result, true); 57 expect(mockPlatform.launchedUrls, contains('https://example.com')); 58 }); 59 60 test('scheme check is case insensitive', () async { 61 final result = await UrlLauncher.launchExternalUrl( 62 'HTTPS://example.com', 63 ); 64 expect(result, true); 65 // URL gets normalized to lowercase by url_launcher 66 expect(mockPlatform.launchedUrls, contains('https://example.com')); 67 }); 68 }); 69 70 group('Invalid URL Handling', () { 71 test('returns false for malformed URLs', () async { 72 final result = await UrlLauncher.launchExternalUrl('not a url'); 73 expect(result, false); 74 }); 75 76 test('returns false for empty string', () async { 77 final result = await UrlLauncher.launchExternalUrl(''); 78 expect(result, false); 79 }); 80 81 test('handles URLs with special characters', () async { 82 final result = await UrlLauncher.launchExternalUrl( 83 'https://example.com/path?query=value&other=123', 84 ); 85 expect(result, true); 86 }); 87 }); 88 89 group('Error Snackbar Display', () { 90 testWidgets('shows snackbar when context provided and URL blocked', ( 91 tester, 92 ) async { 93 await tester.pumpWidget( 94 MaterialApp( 95 home: Scaffold( 96 body: Builder( 97 builder: (context) { 98 return ElevatedButton( 99 onPressed: () async { 100 await UrlLauncher.launchExternalUrl( 101 'javascript:alert("xss")', 102 context: context, 103 ); 104 }, 105 child: const Text('Test'), 106 ); 107 }, 108 ), 109 ), 110 ), 111 ); 112 113 // Tap button to trigger URL launch 114 await tester.tap(find.byType(ElevatedButton)); 115 await tester.pump(); 116 117 // Wait for snackbar animation 118 await tester.pumpAndSettle(); 119 120 // Verify snackbar is displayed 121 expect(find.text('Invalid link format'), findsOneWidget); 122 }); 123 124 testWidgets('shows snackbar when context provided and URL fails', ( 125 tester, 126 ) async { 127 // Configure platform to fail 128 mockPlatform.canLaunchResponse = false; 129 130 await tester.pumpWidget( 131 MaterialApp( 132 home: Scaffold( 133 body: Builder( 134 builder: (context) { 135 return ElevatedButton( 136 onPressed: () async { 137 await UrlLauncher.launchExternalUrl( 138 'https://example.com', 139 context: context, 140 ); 141 }, 142 child: const Text('Test'), 143 ); 144 }, 145 ), 146 ), 147 ), 148 ); 149 150 // Tap button to trigger URL launch 151 await tester.tap(find.byType(ElevatedButton)); 152 await tester.pump(); 153 154 // Wait for snackbar animation 155 await tester.pumpAndSettle(); 156 157 // Verify snackbar is displayed 158 expect(find.text('Could not open link'), findsOneWidget); 159 }); 160 161 test('does not crash when context is null', () async { 162 // Should not throw exception 163 expect( 164 () async => UrlLauncher.launchExternalUrl('javascript:alert("xss")'), 165 returnsNormally, 166 ); 167 }); 168 }); 169 170 group('Successful Launches', () { 171 test('successfully launches valid https URL', () async { 172 final result = await UrlLauncher.launchExternalUrl( 173 'https://www.example.com/path', 174 ); 175 expect(result, true); 176 expect( 177 mockPlatform.launchedUrls, 178 contains('https://www.example.com/path'), 179 ); 180 }); 181 182 test('uses external application mode', () async { 183 await UrlLauncher.launchExternalUrl('https://example.com'); 184 expect( 185 mockPlatform.lastLaunchMode, 186 PreferredLaunchMode.externalApplication, 187 ); 188 }); 189 }); 190 }); 191}