···
1
+
import 'dart:convert';
3
+
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';
8
+
import 'package:http/http.dart' as http;
9
+
import 'package:mockito/annotations.dart';
10
+
import 'package:mockito/mockito.dart';
12
+
import 'vote_service_test.mocks.dart';
14
+
// Generate mocks for OAuthSession
15
+
@GenerateMocks([OAuthSession])
group('VoteService', () {
18
+
group('_findExistingVote pagination', () {
19
+
test('should find vote in first page', () async {
20
+
final mockSession = MockOAuthSession();
21
+
final service = VoteService(
22
+
sessionGetter: () async => mockSession,
23
+
didGetter: () => 'did:plc:test',
24
+
pdsUrlGetter: () => 'https://test.pds',
27
+
// Mock first page response with matching vote
28
+
final firstPageResponse = http.Response(
32
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/abc123',
35
+
'uri': 'at://did:plc:author/social.coves.post.record/post1',
39
+
'createdAt': '2024-01-01T00:00:00Z',
49
+
mockSession.fetchHandler(
50
+
argThat(contains('listRecords')),
53
+
).thenAnswer((_) async => firstPageResponse);
55
+
// Mock deleteRecord for when existing vote is found
57
+
mockSession.fetchHandler(
58
+
argThat(contains('deleteRecord')),
60
+
headers: anyNamed('headers'),
61
+
body: anyNamed('body'),
64
+
(_) async => http.Response(jsonEncode({}), 200),
67
+
// Test that vote is found via reflection (private method)
68
+
// This is verified indirectly through createVote behavior
69
+
final response = await service.createVote(
70
+
postUri: 'at://did:plc:author/social.coves.post.record/post1',
75
+
// Should return deleted=true because existing vote with same direction
76
+
expect(response.deleted, true);
78
+
mockSession.fetchHandler(
79
+
argThat(contains('listRecords')),
85
+
test('should paginate through multiple pages to find vote', () async {
86
+
final mockSession = MockOAuthSession();
87
+
final service = VoteService(
88
+
sessionGetter: () async => mockSession,
89
+
didGetter: () => 'did:plc:test',
90
+
pdsUrlGetter: () => 'https://test.pds',
93
+
// Mock first page without matching vote but with cursor
94
+
final firstPageResponse = http.Response(
98
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/abc1',
101
+
'uri': 'at://did:plc:author/social.coves.post.record/other1',
108
+
'cursor': 'cursor123',
113
+
// Mock second page with matching vote
114
+
final secondPageResponse = http.Response(
118
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/abc123',
121
+
'uri': 'at://did:plc:author/social.coves.post.record/target',
125
+
'createdAt': '2024-01-01T00:00:00Z',
134
+
// Setup mock responses based on URL
136
+
mockSession.fetchHandler(
137
+
argThat(allOf(contains('listRecords'), isNot(contains('cursor')))),
140
+
).thenAnswer((_) async => firstPageResponse);
143
+
mockSession.fetchHandler(
144
+
argThat(allOf(contains('listRecords'), contains('cursor=cursor123'))),
147
+
).thenAnswer((_) async => secondPageResponse);
149
+
// Mock deleteRecord for when existing vote is found
151
+
mockSession.fetchHandler(
152
+
argThat(contains('deleteRecord')),
154
+
headers: anyNamed('headers'),
155
+
body: anyNamed('body'),
158
+
(_) async => http.Response(jsonEncode({}), 200),
161
+
// Test that pagination works by creating vote that exists on page 2
162
+
final response = await service.createVote(
163
+
postUri: 'at://did:plc:author/social.coves.post.record/target',
164
+
postCid: 'bafy123',
168
+
// Should return deleted=true because existing vote was found on page 2
169
+
expect(response.deleted, true);
171
+
// Verify both pages were fetched
173
+
mockSession.fetchHandler(
174
+
argThat(allOf(contains('listRecords'), isNot(contains('cursor')))),
180
+
mockSession.fetchHandler(
181
+
argThat(allOf(contains('listRecords'), contains('cursor=cursor123'))),
187
+
test('should handle vote not found after pagination', () async {
188
+
final mockSession = MockOAuthSession();
189
+
final service = VoteService(
190
+
sessionGetter: () async => mockSession,
191
+
didGetter: () => 'did:plc:test',
192
+
pdsUrlGetter: () => 'https://test.pds',
195
+
// Mock response with no matching votes
196
+
final response = http.Response(
200
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/abc1',
203
+
'uri': 'at://did:plc:author/social.coves.post.record/other',
216
+
mockSession.fetchHandler(
217
+
argThat(contains('listRecords')),
220
+
).thenAnswer((_) async => response);
222
+
// Mock createRecord for new vote
224
+
mockSession.fetchHandler(
225
+
argThat(contains('createRecord')),
227
+
headers: anyNamed('headers'),
228
+
body: anyNamed('body'),
231
+
(_) async => http.Response(
233
+
'uri': 'at://did:plc:test/social.coves.interaction.vote/new123',
240
+
// Test creating vote for post not in vote history
241
+
final voteResponse = await service.createVote(
242
+
postUri: 'at://did:plc:author/social.coves.post.record/newpost',
243
+
postCid: 'bafy123',
247
+
// Should create new vote
248
+
expect(voteResponse.deleted, false);
249
+
expect(voteResponse.uri, isNotNull);
250
+
expect(voteResponse.cid, 'bafy456');
252
+
// Verify createRecord was called
254
+
mockSession.fetchHandler(
255
+
argThat(contains('createRecord')),
257
+
headers: anyNamed('headers'),
258
+
body: anyNamed('body'),
test('should create vote successfully', () async {