refactor: update providers for new OAuth patterns

Update FeedProvider and CommentsProvider to work with the simplified
session model and backend-proxied auth flow.

Key changes:
- Use VoteService callback pattern instead of OAuthSession
- Remove direct PDS URL handling
- Simplify test mocks to match new API

Provider updates:
- FeedProvider: Use token getter instead of session getter
- CommentsProvider: Same simplification

Test updates:
- Update mocks to use CovesSession instead of OAuthSession
- Remove PDS URL getter mocks
- Simplify vote service setup in tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+7 -2
lib/providers/comments_provider.dart
···
}) : _voteProvider = voteProvider,
_voteService = voteService {
// Use injected service (for testing) or create new one (for production)
-
// Pass token getter to API service for automatic fresh token retrieval
_apiService =
apiService ??
-
CovesApiService(tokenGetter: _authProvider.getAccessToken);
// Track initial auth state
_wasAuthenticated = _authProvider.isAuthenticated;
···
}) : _voteProvider = voteProvider,
_voteService = voteService {
// Use injected service (for testing) or create new one (for production)
+
// Pass token getter, refresh handler, and sign out handler to API service
+
// for automatic fresh token retrieval and automatic token refresh on 401
_apiService =
apiService ??
+
CovesApiService(
+
tokenGetter: _authProvider.getAccessToken,
+
tokenRefresher: _authProvider.refreshToken,
+
signOutHandler: _authProvider.signOut,
+
);
// Track initial auth state
_wasAuthenticated = _authProvider.isAuthenticated;
+7 -2
lib/providers/feed_provider.dart
···
}) : _voteProvider = voteProvider,
_voteService = voteService {
// Use injected service (for testing) or create new one (for production)
-
// Pass token getter to API service for automatic fresh token retrieval
_apiService =
apiService ??
-
CovesApiService(tokenGetter: _authProvider.getAccessToken);
// Track initial auth state
_wasAuthenticated = _authProvider.isAuthenticated;
···
}) : _voteProvider = voteProvider,
_voteService = voteService {
// Use injected service (for testing) or create new one (for production)
+
// Pass token getter, refresh handler, and sign out handler to API service
+
// for automatic fresh token retrieval and automatic token refresh on 401
_apiService =
apiService ??
+
CovesApiService(
+
tokenGetter: _authProvider.getAccessToken,
+
tokenRefresher: _authProvider.refreshToken,
+
signOutHandler: _authProvider.signOut,
+
);
// Track initial auth state
_wasAuthenticated = _authProvider.isAuthenticated;
+5 -3
test/providers/comments_provider_test.dart
···
refresh: true,
);
-
// Try to load again while still loading
await commentsProvider.loadComments(
postUri: testPostUri,
refresh: true,
);
await firstFuture;
-
// Should only have called API once
verify(
mockApiService.getComments(
postUri: anyNamed('postUri'),
···
limit: anyNamed('limit'),
cursor: anyNamed('cursor'),
),
-
).called(1);
});
test('should load vote state when authenticated', () async {
···
refresh: true,
);
+
// Try to load again while still loading - should schedule a refresh
await commentsProvider.loadComments(
postUri: testPostUri,
refresh: true,
);
await firstFuture;
+
// Wait a bit for the pending refresh to execute
+
await Future.delayed(const Duration(milliseconds: 200));
+
// Should have called API twice - once for initial load, once for pending refresh
verify(
mockApiService.getComments(
postUri: anyNamed('postUri'),
···
limit: anyNamed('limit'),
cursor: anyNamed('cursor'),
),
+
).called(2);
});
test('should load vote state when authenticated', () async {
+14 -6
test/providers/comments_provider_test.mocks.dart
···
as _i6.Future<void>);
@override
-
void clearError() => super.noSuchMethod(
-
Invocation.method(#clearError, []),
-
returnValueForMissingStub: null,
-
);
@override
-
void dispose() => super.noSuchMethod(
-
Invocation.method(#dispose, []),
returnValueForMissingStub: null,
);
···
@override
void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#removeListener, [listener]),
returnValueForMissingStub: null,
);
···
as _i6.Future<void>);
@override
+
_i6.Future<bool> refreshToken() =>
+
(super.noSuchMethod(
+
Invocation.method(#refreshToken, []),
+
returnValue: _i6.Future<bool>.value(false),
+
)
+
as _i6.Future<bool>);
@override
+
void clearError() => super.noSuchMethod(
+
Invocation.method(#clearError, []),
returnValueForMissingStub: null,
);
···
@override
void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#removeListener, [listener]),
+
returnValueForMissingStub: null,
+
);
+
+
@override
+
void dispose() => super.noSuchMethod(
+
Invocation.method(#dispose, []),
returnValueForMissingStub: null,
);
+14 -6
test/providers/feed_provider_test.mocks.dart
···
as _i5.Future<void>);
@override
-
void clearError() => super.noSuchMethod(
-
Invocation.method(#clearError, []),
-
returnValueForMissingStub: null,
-
);
@override
-
void dispose() => super.noSuchMethod(
-
Invocation.method(#dispose, []),
returnValueForMissingStub: null,
);
···
@override
void removeListener(_i6.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#removeListener, [listener]),
returnValueForMissingStub: null,
);
···
as _i5.Future<void>);
@override
+
_i5.Future<bool> refreshToken() =>
+
(super.noSuchMethod(
+
Invocation.method(#refreshToken, []),
+
returnValue: _i5.Future<bool>.value(false),
+
)
+
as _i5.Future<bool>);
@override
+
void clearError() => super.noSuchMethod(
+
Invocation.method(#clearError, []),
returnValueForMissingStub: null,
);
···
@override
void removeListener(_i6.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#removeListener, [listener]),
+
returnValueForMissingStub: null,
+
);
+
+
@override
+
void dispose() => super.noSuchMethod(
+
Invocation.method(#dispose, []),
returnValueForMissingStub: null,
);
+14 -6
test/providers/vote_provider_test.mocks.dart
···
as _i3.Future<void>);
@override
-
void clearError() => super.noSuchMethod(
-
Invocation.method(#clearError, []),
-
returnValueForMissingStub: null,
-
);
@override
-
void dispose() => super.noSuchMethod(
-
Invocation.method(#dispose, []),
returnValueForMissingStub: null,
);
···
@override
void removeListener(_i5.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#removeListener, [listener]),
returnValueForMissingStub: null,
);
···
as _i3.Future<void>);
@override
+
_i3.Future<bool> refreshToken() =>
+
(super.noSuchMethod(
+
Invocation.method(#refreshToken, []),
+
returnValue: _i3.Future<bool>.value(false),
+
)
+
as _i3.Future<bool>);
@override
+
void clearError() => super.noSuchMethod(
+
Invocation.method(#clearError, []),
returnValueForMissingStub: null,
);
···
@override
void removeListener(_i5.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#removeListener, [listener]),
+
returnValueForMissingStub: null,
+
);
+
+
@override
+
void dispose() => super.noSuchMethod(
+
Invocation.method(#dispose, []),
returnValueForMissingStub: null,
);
+3 -7
test/test_helpers/mock_providers.dart
···
-
import 'package:atproto_oauth_flutter/atproto_oauth_flutter.dart';
import 'package:coves_flutter/providers/vote_provider.dart';
import 'package:coves_flutter/services/vote_service.dart';
import 'package:flutter/foundation.dart';
···
String? _error;
String? _did;
String? _handle;
-
OAuthSession? _session;
bool get isAuthenticated => _isAuthenticated;
bool get isLoading => _isLoading;
String? get error => _error;
String? get did => _did;
String? get handle => _handle;
-
OAuthSession? get session => _session;
void setAuthenticated({required bool value, String? did}) {
_isAuthenticated = value;
···
Future<String?> getAccessToken() async {
return _isAuthenticated ? 'mock_access_token' : null;
-
}
-
-
String? getPdsUrl() {
-
return _isAuthenticated ? 'https://mock.pds.host' : null;
}
void clearError() {
···
+
import 'package:coves_flutter/models/coves_session.dart';
import 'package:coves_flutter/providers/vote_provider.dart';
import 'package:coves_flutter/services/vote_service.dart';
import 'package:flutter/foundation.dart';
···
String? _error;
String? _did;
String? _handle;
+
CovesSession? _session;
bool get isAuthenticated => _isAuthenticated;
bool get isLoading => _isLoading;
String? get error => _error;
String? get did => _did;
String? get handle => _handle;
+
CovesSession? get session => _session;
void setAuthenticated({required bool value, String? did}) {
_isAuthenticated = value;
···
Future<String?> getAccessToken() async {
return _isAuthenticated ? 'mock_access_token' : null;
}
void clearError() {