at main 8.6 kB view raw
1import 'package:coves_flutter/models/coves_session.dart'; 2import 'package:coves_flutter/providers/auth_provider.dart'; 3import 'package:coves_flutter/services/coves_auth_service.dart'; 4import 'package:flutter_test/flutter_test.dart'; 5import 'package:mockito/annotations.dart'; 6import 'package:mockito/mockito.dart'; 7 8import 'auth_provider_test.mocks.dart'; 9 10// Generate mocks for CovesAuthService 11@GenerateMocks([CovesAuthService]) 12void main() { 13 TestWidgetsFlutterBinding.ensureInitialized(); 14 15 group('AuthProvider', () { 16 late AuthProvider authProvider; 17 late MockCovesAuthService mockAuthService; 18 19 setUp(() { 20 // Create mock auth service 21 mockAuthService = MockCovesAuthService(); 22 23 // Create auth provider with injected mock service 24 authProvider = AuthProvider(authService: mockAuthService); 25 }); 26 27 group('initialize', () { 28 test('should initialize with no stored session', () async { 29 when(mockAuthService.initialize()).thenAnswer((_) async => {}); 30 when(mockAuthService.restoreSession()).thenAnswer((_) async => null); 31 32 await authProvider.initialize(); 33 34 expect(authProvider.isAuthenticated, false); 35 expect(authProvider.isLoading, false); 36 expect(authProvider.session, null); 37 expect(authProvider.error, null); 38 }); 39 40 test('should restore session if available', () async { 41 const mockSession = CovesSession( 42 token: 'mock_sealed_token', 43 did: 'did:plc:test123', 44 sessionId: 'session123', 45 handle: 'test.user', 46 ); 47 48 when(mockAuthService.initialize()).thenAnswer((_) async => {}); 49 when( 50 mockAuthService.restoreSession(), 51 ).thenAnswer((_) async => mockSession); 52 53 await authProvider.initialize(); 54 55 expect(authProvider.isAuthenticated, true); 56 expect(authProvider.did, 'did:plc:test123'); 57 expect(authProvider.handle, 'test.user'); 58 }); 59 60 test('should handle initialization errors gracefully', () async { 61 when(mockAuthService.initialize()).thenThrow(Exception('Init failed')); 62 63 await authProvider.initialize(); 64 65 expect(authProvider.isAuthenticated, false); 66 expect(authProvider.error, isNotNull); 67 expect(authProvider.isLoading, false); 68 }); 69 }); 70 71 group('signIn', () { 72 test('should sign in successfully with valid handle', () async { 73 const mockSession = CovesSession( 74 token: 'mock_sealed_token', 75 did: 'did:plc:test123', 76 sessionId: 'session123', 77 handle: 'alice.bsky.social', 78 ); 79 80 when( 81 mockAuthService.signIn('alice.bsky.social'), 82 ).thenAnswer((_) async => mockSession); 83 84 await authProvider.signIn('alice.bsky.social'); 85 86 expect(authProvider.isAuthenticated, true); 87 expect(authProvider.did, 'did:plc:test123'); 88 expect(authProvider.handle, 'alice.bsky.social'); 89 expect(authProvider.error, null); 90 }); 91 92 test('should reject empty handle', () async { 93 expect(() => authProvider.signIn(''), throwsA(isA<Exception>())); 94 95 expect(authProvider.isAuthenticated, false); 96 }); 97 98 test('should handle sign in errors', () async { 99 when( 100 mockAuthService.signIn('invalid.handle'), 101 ).thenThrow(Exception('Sign in failed')); 102 103 expect( 104 () => authProvider.signIn('invalid.handle'), 105 throwsA(isA<Exception>()), 106 ); 107 108 expect(authProvider.isAuthenticated, false); 109 expect(authProvider.error, isNotNull); 110 }); 111 }); 112 113 group('signOut', () { 114 test('should sign out and clear state', () async { 115 // First sign in 116 const mockSession = CovesSession( 117 token: 'mock_sealed_token', 118 did: 'did:plc:test123', 119 sessionId: 'session123', 120 handle: 'alice.bsky.social', 121 ); 122 when( 123 mockAuthService.signIn('alice.bsky.social'), 124 ).thenAnswer((_) async => mockSession); 125 126 await authProvider.signIn('alice.bsky.social'); 127 expect(authProvider.isAuthenticated, true); 128 129 // Then sign out 130 when(mockAuthService.signOut()).thenAnswer((_) async => {}); 131 132 await authProvider.signOut(); 133 134 expect(authProvider.isAuthenticated, false); 135 expect(authProvider.session, null); 136 expect(authProvider.did, null); 137 expect(authProvider.handle, null); 138 }); 139 140 test('should clear state even if server revocation fails', () async { 141 // Sign in first 142 const mockSession = CovesSession( 143 token: 'mock_sealed_token', 144 did: 'did:plc:test123', 145 sessionId: 'session123', 146 handle: 'alice.bsky.social', 147 ); 148 when( 149 mockAuthService.signIn('alice.bsky.social'), 150 ).thenAnswer((_) async => mockSession); 151 152 await authProvider.signIn('alice.bsky.social'); 153 154 // Sign out with error 155 when( 156 mockAuthService.signOut(), 157 ).thenThrow(Exception('Revocation failed')); 158 159 await authProvider.signOut(); 160 161 expect(authProvider.isAuthenticated, false); 162 expect(authProvider.session, null); 163 }); 164 }); 165 166 group('getAccessToken', () { 167 test('should return null when not authenticated', () async { 168 final token = await authProvider.getAccessToken(); 169 expect(token, null); 170 }); 171 172 test('should return sealed token when authenticated', () async { 173 const mockSession = CovesSession( 174 token: 'mock_sealed_token', 175 did: 'did:plc:test123', 176 sessionId: 'session123', 177 ); 178 179 when( 180 mockAuthService.signIn('alice.bsky.social'), 181 ).thenAnswer((_) async => mockSession); 182 183 await authProvider.signIn('alice.bsky.social'); 184 185 final token = await authProvider.getAccessToken(); 186 expect(token, 'mock_sealed_token'); 187 }); 188 }); 189 190 group('refreshToken', () { 191 test('should return false when not authenticated', () async { 192 final result = await authProvider.refreshToken(); 193 expect(result, false); 194 }); 195 196 test('should refresh token successfully', () async { 197 const mockSession = CovesSession( 198 token: 'mock_sealed_token', 199 did: 'did:plc:test123', 200 sessionId: 'session123', 201 ); 202 const refreshedSession = CovesSession( 203 token: 'new_sealed_token', 204 did: 'did:plc:test123', 205 sessionId: 'session123', 206 ); 207 208 when( 209 mockAuthService.signIn('alice.bsky.social'), 210 ).thenAnswer((_) async => mockSession); 211 when( 212 mockAuthService.refreshToken(), 213 ).thenAnswer((_) async => refreshedSession); 214 215 await authProvider.signIn('alice.bsky.social'); 216 final result = await authProvider.refreshToken(); 217 218 expect(result, true); 219 expect(authProvider.session?.token, 'new_sealed_token'); 220 }); 221 222 test('should sign out if refresh fails', () async { 223 const mockSession = CovesSession( 224 token: 'mock_sealed_token', 225 did: 'did:plc:test123', 226 sessionId: 'session123', 227 ); 228 229 when( 230 mockAuthService.signIn('alice.bsky.social'), 231 ).thenAnswer((_) async => mockSession); 232 when( 233 mockAuthService.refreshToken(), 234 ).thenThrow(Exception('Refresh failed')); 235 when(mockAuthService.signOut()).thenAnswer((_) async => {}); 236 237 await authProvider.signIn('alice.bsky.social'); 238 final result = await authProvider.refreshToken(); 239 240 expect(result, false); 241 expect(authProvider.isAuthenticated, false); 242 }); 243 }); 244 245 group('State Management', () { 246 test('should notify listeners on state change', () async { 247 var notificationCount = 0; 248 authProvider.addListener(() { 249 notificationCount++; 250 }); 251 252 const mockSession = CovesSession( 253 token: 'mock_sealed_token', 254 did: 'did:plc:test123', 255 sessionId: 'session123', 256 ); 257 when( 258 mockAuthService.signIn('alice.bsky.social'), 259 ).thenAnswer((_) async => mockSession); 260 261 await authProvider.signIn('alice.bsky.social'); 262 263 // Should notify during sign in process 264 expect(notificationCount, greaterThan(0)); 265 }); 266 267 test('should clear error when clearError is called', () { 268 // Trigger an error state 269 authProvider.clearError(); 270 expect(authProvider.error, null); 271 }); 272 }); 273 }); 274}