···
1
-
import 'package:atproto_oauth_flutter/atproto_oauth_flutter.dart';
1
+
import 'package:coves_flutter/models/coves_session.dart';
import 'package:coves_flutter/providers/auth_provider.dart';
3
-
import 'package:coves_flutter/services/oauth_service.dart';
3
+
import 'package:coves_flutter/services/coves_auth_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
7
-
import 'package:shared_preferences/shared_preferences.dart';
import 'auth_provider_test.mocks.dart';
11
-
// Generate mocks for OAuthService and OAuthSession only
12
-
@GenerateMocks([OAuthService, OAuthSession])
10
+
// Generate mocks for CovesAuthService
11
+
@GenerateMocks([CovesAuthService])
TestWidgetsFlutterBinding.ensureInitialized();
group('AuthProvider', () {
late AuthProvider authProvider;
18
-
late MockOAuthService mockOAuthService;
17
+
late MockCovesAuthService mockAuthService;
21
-
// Mock SharedPreferences
22
-
SharedPreferences.setMockInitialValues({});
24
-
// Create mock OAuth service
25
-
mockOAuthService = MockOAuthService();
20
+
// Create mock auth service
21
+
mockAuthService = MockCovesAuthService();
// Create auth provider with injected mock service
28
-
authProvider = AuthProvider(oauthService: mockOAuthService);
32
-
authProvider.dispose();
24
+
authProvider = AuthProvider(authService: mockAuthService);
test('should initialize with no stored session', () async {
37
-
when(mockOAuthService.initialize()).thenAnswer((_) async => {});
29
+
when(mockAuthService.initialize()).thenAnswer((_) async => {});
30
+
when(mockAuthService.restoreSession()).thenAnswer((_) async => null);
await authProvider.initialize();
···
expect(authProvider.error, null);
47
-
test('should restore session if DID is stored', () async {
48
-
// Set up mock stored DID
49
-
SharedPreferences.setMockInitialValues({
50
-
'current_user_did': 'did:plc:test123',
53
-
final mockSession = MockOAuthSession();
54
-
when(mockSession.sub).thenReturn('did:plc:test123');
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',
56
-
when(mockOAuthService.initialize()).thenAnswer((_) async => {});
48
+
when(mockAuthService.initialize()).thenAnswer((_) async => {});
58
-
mockOAuthService.restoreSession('did:plc:test123'),
50
+
mockAuthService.restoreSession(),
).thenAnswer((_) async => mockSession);
await authProvider.initialize();
expect(authProvider.isAuthenticated, true);
expect(authProvider.did, 'did:plc:test123');
57
+
expect(authProvider.handle, 'test.user');
test('should handle initialization errors gracefully', () async {
68
-
when(mockOAuthService.initialize()).thenThrow(Exception('Init failed'));
61
+
when(mockAuthService.initialize()).thenThrow(Exception('Init failed'));
await authProvider.initialize();
···
test('should sign in successfully with valid handle', () async {
80
-
final mockSession = MockOAuthSession();
81
-
when(mockSession.sub).thenReturn('did:plc:test123');
73
+
const mockSession = CovesSession(
74
+
token: 'mock_sealed_token',
75
+
did: 'did:plc:test123',
76
+
sessionId: 'session123',
77
+
handle: 'alice.bsky.social',
84
-
mockOAuthService.signIn('alice.bsky.social'),
81
+
mockAuthService.signIn('alice.bsky.social'),
).thenAnswer((_) async => mockSession);
await authProvider.signIn('alice.bsky.social');
···
test('should handle sign in errors', () async {
103
-
mockOAuthService.signIn('invalid.handle'),
100
+
mockAuthService.signIn('invalid.handle'),
).thenThrow(Exception('Sign in failed'));
···
expect(authProvider.isAuthenticated, false);
expect(authProvider.error, isNotNull);
115
-
test('should store DID in SharedPreferences after sign in', () async {
116
-
final mockSession = MockOAuthSession();
117
-
when(mockSession.sub).thenReturn('did:plc:test123');
120
-
mockOAuthService.signIn('alice.bsky.social'),
121
-
).thenAnswer((_) async => mockSession);
123
-
await authProvider.signIn('alice.bsky.social');
125
-
final prefs = await SharedPreferences.getInstance();
126
-
expect(prefs.getString('current_user_did'), 'did:plc:test123');
test('should sign out and clear state', () async {
133
-
final mockSession = MockOAuthSession();
134
-
when(mockSession.sub).thenReturn('did:plc:test123');
116
+
const mockSession = CovesSession(
117
+
token: 'mock_sealed_token',
118
+
did: 'did:plc:test123',
119
+
sessionId: 'session123',
120
+
handle: 'alice.bsky.social',
136
-
mockOAuthService.signIn('alice.bsky.social'),
123
+
mockAuthService.signIn('alice.bsky.social'),
).thenAnswer((_) async => mockSession);
await authProvider.signIn('alice.bsky.social');
expect(authProvider.isAuthenticated, true);
144
-
mockOAuthService.signOut('did:plc:test123'),
145
-
).thenAnswer((_) async => {});
130
+
when(mockAuthService.signOut()).thenAnswer((_) async => {});
await authProvider.signOut();
···
expect(authProvider.handle, null);
155
-
test('should clear DID from SharedPreferences', () async {
140
+
test('should clear state even if server revocation fails', () async {
157
-
final mockSession = MockOAuthSession();
158
-
when(mockSession.sub).thenReturn('did:plc:test123');
142
+
const mockSession = CovesSession(
143
+
token: 'mock_sealed_token',
144
+
did: 'did:plc:test123',
145
+
sessionId: 'session123',
146
+
handle: 'alice.bsky.social',
160
-
mockOAuthService.signIn('alice.bsky.social'),
149
+
mockAuthService.signIn('alice.bsky.social'),
).thenAnswer((_) async => mockSession);
await authProvider.signIn('alice.bsky.social');
167
-
mockOAuthService.signOut('did:plc:test123'),
168
-
).thenAnswer((_) async => {});
154
+
// Sign out with error
155
+
when(mockAuthService.signOut())
156
+
.thenThrow(Exception('Revocation failed'));
await authProvider.signOut();
172
-
final prefs = await SharedPreferences.getInstance();
173
-
expect(prefs.getString('current_user_did'), null);
160
+
expect(authProvider.isAuthenticated, false);
161
+
expect(authProvider.session, null);
165
+
group('getAccessToken', () {
166
+
test('should return null when not authenticated', () async {
167
+
final token = await authProvider.getAccessToken();
168
+
expect(token, null);
176
-
test('should clear state even if server revocation fails', () async {
178
-
final mockSession = MockOAuthSession();
179
-
when(mockSession.sub).thenReturn('did:plc:test123');
171
+
test('should return sealed token when authenticated', () async {
172
+
const mockSession = CovesSession(
173
+
token: 'mock_sealed_token',
174
+
did: 'did:plc:test123',
175
+
sessionId: 'session123',
181
-
mockOAuthService.signIn('alice.bsky.social'),
179
+
mockAuthService.signIn('alice.bsky.social'),
).thenAnswer((_) async => mockSession);
await authProvider.signIn('alice.bsky.social');
186
-
// Sign out with error
184
+
final token = await authProvider.getAccessToken();
185
+
expect(token, 'mock_sealed_token');
189
+
group('refreshToken', () {
190
+
test('should return false when not authenticated', () async {
191
+
final result = await authProvider.refreshToken();
192
+
expect(result, false);
195
+
test('should refresh token successfully', () async {
196
+
const mockSession = CovesSession(
197
+
token: 'mock_sealed_token',
198
+
did: 'did:plc:test123',
199
+
sessionId: 'session123',
201
+
const refreshedSession = CovesSession(
202
+
token: 'new_sealed_token',
203
+
did: 'did:plc:test123',
204
+
sessionId: 'session123',
188
-
mockOAuthService.signOut('did:plc:test123'),
189
-
).thenThrow(Exception('Revocation failed'));
208
+
mockAuthService.signIn('alice.bsky.social'),
209
+
).thenAnswer((_) async => mockSession);
211
+
mockAuthService.refreshToken(),
212
+
).thenAnswer((_) async => refreshedSession);
191
-
await authProvider.signOut();
214
+
await authProvider.signIn('alice.bsky.social');
215
+
final result = await authProvider.refreshToken();
193
-
expect(authProvider.isAuthenticated, false);
194
-
expect(authProvider.session, null);
217
+
expect(result, true);
218
+
expect(authProvider.session?.token, 'new_sealed_token');
198
-
group('getAccessToken', () {
199
-
test('should return null when not authenticated', () async {
200
-
final token = await authProvider.getAccessToken();
201
-
expect(token, null);
221
+
test('should sign out if refresh fails', () async {
222
+
const mockSession = CovesSession(
223
+
token: 'mock_sealed_token',
224
+
did: 'did:plc:test123',
225
+
sessionId: 'session123',
204
-
// Note: Testing getAccessToken requires mocking internal OAuth classes
205
-
// that are not exported from atproto_oauth_flutter package.
206
-
// These tests would need integration testing or a different approach.
229
+
mockAuthService.signIn('alice.bsky.social'),
230
+
).thenAnswer((_) async => mockSession);
232
+
mockAuthService.refreshToken(),
233
+
).thenThrow(Exception('Refresh failed'));
234
+
when(mockAuthService.signOut()).thenAnswer((_) async => {});
208
-
test('should return null when not authenticated '
209
-
'(skipped - needs integration test)', () async {
210
-
// This test is skipped as it requires mocking internal OAuth classes
211
-
// that cannot be mocked with mockito
236
+
await authProvider.signIn('alice.bsky.social');
237
+
final result = await authProvider.refreshToken();
214
-
test('should sign out user if token refresh fails '
215
-
'(skipped - needs integration test)', () async {
216
-
// This test demonstrates the critical fix for issue #7
217
-
// Token refresh failure should trigger sign out
218
-
// Skipped as it requires mocking internal OAuth classes
239
+
expect(result, false);
240
+
expect(authProvider.isAuthenticated, false);
group('State Management', () {
···
229
-
final mockSession = MockOAuthSession();
230
-
when(mockSession.sub).thenReturn('did:plc:test123');
251
+
const mockSession = CovesSession(
252
+
token: 'mock_sealed_token',
253
+
did: 'did:plc:test123',
254
+
sessionId: 'session123',
232
-
mockOAuthService.signIn('alice.bsky.social'),
257
+
mockAuthService.signIn('alice.bsky.social'),
).thenAnswer((_) async => mockSession);
await authProvider.signIn('alice.bsky.social');
···
test('should clear error when clearError is called', () {
242
-
// Simulate an error state
243
-
when(mockOAuthService.signIn('invalid')).thenThrow(Exception('Error'));
245
-
// This would set error state
267
+
// Trigger an error state
authProvider.clearError();
expect(authProvider.error, null);