···
+
import 'package:atproto_oauth_flutter/atproto_oauth_flutter.dart';
import 'package:coves_flutter/services/api_exceptions.dart';
import 'package:coves_flutter/services/vote_service.dart';
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
+
import 'package:http/http.dart' as http;
+
import 'package:mockito/annotations.dart';
+
import 'package:mockito/mockito.dart';
+
import 'vote_service_test.mocks.dart';
+
// Generate mocks for OAuthSession
+
@GenerateMocks([OAuthSession])
group('VoteService', () {
+
group('_findExistingVote pagination', () {
+
test('should find vote in first page', () async {
+
final mockSession = MockOAuthSession();
+
final service = VoteService(
+
sessionGetter: () async => mockSession,
+
didGetter: () => 'did:plc:test',
+
pdsUrlGetter: () => 'https://test.pds',
+
// Mock first page response with matching vote
+
final firstPageResponse = http.Response(
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/abc123',
+
'uri': 'at://did:plc:author/social.coves.post.record/post1',
+
'createdAt': '2024-01-01T00:00:00Z',
+
mockSession.fetchHandler(
+
argThat(contains('listRecords')),
+
).thenAnswer((_) async => firstPageResponse);
+
// Mock deleteRecord for when existing vote is found
+
mockSession.fetchHandler(
+
argThat(contains('deleteRecord')),
+
headers: anyNamed('headers'),
+
body: anyNamed('body'),
+
(_) async => http.Response(jsonEncode({}), 200),
+
// Test that vote is found via reflection (private method)
+
// This is verified indirectly through createVote behavior
+
final response = await service.createVote(
+
postUri: 'at://did:plc:author/social.coves.post.record/post1',
+
// Should return deleted=true because existing vote with same direction
+
expect(response.deleted, true);
+
mockSession.fetchHandler(
+
argThat(contains('listRecords')),
+
test('should paginate through multiple pages to find vote', () async {
+
final mockSession = MockOAuthSession();
+
final service = VoteService(
+
sessionGetter: () async => mockSession,
+
didGetter: () => 'did:plc:test',
+
pdsUrlGetter: () => 'https://test.pds',
+
// Mock first page without matching vote but with cursor
+
final firstPageResponse = http.Response(
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/abc1',
+
'uri': 'at://did:plc:author/social.coves.post.record/other1',
+
// Mock second page with matching vote
+
final secondPageResponse = http.Response(
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/abc123',
+
'uri': 'at://did:plc:author/social.coves.post.record/target',
+
'createdAt': '2024-01-01T00:00:00Z',
+
// Setup mock responses based on URL
+
mockSession.fetchHandler(
+
argThat(allOf(contains('listRecords'), isNot(contains('cursor')))),
+
).thenAnswer((_) async => firstPageResponse);
+
mockSession.fetchHandler(
+
argThat(allOf(contains('listRecords'), contains('cursor=cursor123'))),
+
).thenAnswer((_) async => secondPageResponse);
+
// Mock deleteRecord for when existing vote is found
+
mockSession.fetchHandler(
+
argThat(contains('deleteRecord')),
+
headers: anyNamed('headers'),
+
body: anyNamed('body'),
+
(_) async => http.Response(jsonEncode({}), 200),
+
// Test that pagination works by creating vote that exists on page 2
+
final response = await service.createVote(
+
postUri: 'at://did:plc:author/social.coves.post.record/target',
+
// Should return deleted=true because existing vote was found on page 2
+
expect(response.deleted, true);
+
// Verify both pages were fetched
+
mockSession.fetchHandler(
+
argThat(allOf(contains('listRecords'), isNot(contains('cursor')))),
+
mockSession.fetchHandler(
+
argThat(allOf(contains('listRecords'), contains('cursor=cursor123'))),
+
test('should handle vote not found after pagination', () async {
+
final mockSession = MockOAuthSession();
+
final service = VoteService(
+
sessionGetter: () async => mockSession,
+
didGetter: () => 'did:plc:test',
+
pdsUrlGetter: () => 'https://test.pds',
+
// Mock response with no matching votes
+
final response = http.Response(
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/abc1',
+
'uri': 'at://did:plc:author/social.coves.post.record/other',
+
mockSession.fetchHandler(
+
argThat(contains('listRecords')),
+
).thenAnswer((_) async => response);
+
// Mock createRecord for new vote
+
mockSession.fetchHandler(
+
argThat(contains('createRecord')),
+
headers: anyNamed('headers'),
+
body: anyNamed('body'),
+
(_) async => http.Response(
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/new123',
+
// Test creating vote for post not in vote history
+
final voteResponse = await service.createVote(
+
postUri: 'at://did:plc:author/social.coves.post.record/newpost',
+
// Should create new vote
+
expect(voteResponse.deleted, false);
+
expect(voteResponse.uri, isNotNull);
+
expect(voteResponse.cid, 'bafy456');
+
// Verify createRecord was called
+
mockSession.fetchHandler(
+
argThat(contains('createRecord')),
+
headers: anyNamed('headers'),
+
body: anyNamed('body'),
test('should create vote successfully', () async {