Main coves client
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 466 lines 13 kB view raw
1import 'package:coves_flutter/models/community.dart'; 2import 'package:coves_flutter/services/api_exceptions.dart'; 3import 'package:coves_flutter/services/coves_api_service.dart'; 4import 'package:dio/dio.dart'; 5import 'package:flutter_test/flutter_test.dart'; 6import 'package:http_mock_adapter/http_mock_adapter.dart'; 7 8void main() { 9 TestWidgetsFlutterBinding.ensureInitialized(); 10 11 group('CovesApiService - listCommunities', () { 12 late Dio dio; 13 late DioAdapter dioAdapter; 14 late CovesApiService apiService; 15 16 setUp(() { 17 dio = Dio(BaseOptions(baseUrl: 'https://api.test.coves.social')); 18 dioAdapter = DioAdapter(dio: dio); 19 apiService = CovesApiService( 20 dio: dio, 21 tokenGetter: () async => 'test-token', 22 ); 23 }); 24 25 tearDown(() { 26 apiService.dispose(); 27 }); 28 29 test('should successfully fetch communities', () async { 30 final mockResponse = { 31 'communities': [ 32 { 33 'did': 'did:plc:community1', 34 'name': 'test-community-1', 35 'displayName': 'Test Community 1', 36 'subscriberCount': 100, 37 'memberCount': 50, 38 }, 39 { 40 'did': 'did:plc:community2', 41 'name': 'test-community-2', 42 'displayName': 'Test Community 2', 43 'subscriberCount': 200, 44 'memberCount': 100, 45 }, 46 ], 47 'cursor': 'next-cursor', 48 }; 49 50 dioAdapter.onGet( 51 '/xrpc/social.coves.community.list', 52 (server) => server.reply(200, mockResponse), 53 queryParameters: { 54 'limit': 50, 55 'sort': 'popular', 56 }, 57 ); 58 59 final response = await apiService.listCommunities(); 60 61 expect(response, isA<CommunitiesResponse>()); 62 expect(response.communities.length, 2); 63 expect(response.cursor, 'next-cursor'); 64 expect(response.communities[0].did, 'did:plc:community1'); 65 expect(response.communities[0].name, 'test-community-1'); 66 expect(response.communities[1].did, 'did:plc:community2'); 67 }); 68 69 test('should handle empty communities response', () async { 70 final mockResponse = { 71 'communities': [], 72 'cursor': null, 73 }; 74 75 dioAdapter.onGet( 76 '/xrpc/social.coves.community.list', 77 (server) => server.reply(200, mockResponse), 78 queryParameters: { 79 'limit': 50, 80 'sort': 'popular', 81 }, 82 ); 83 84 final response = await apiService.listCommunities(); 85 86 expect(response.communities, isEmpty); 87 expect(response.cursor, null); 88 }); 89 90 test('should handle null communities array', () async { 91 final mockResponse = { 92 'communities': null, 93 'cursor': null, 94 }; 95 96 dioAdapter.onGet( 97 '/xrpc/social.coves.community.list', 98 (server) => server.reply(200, mockResponse), 99 queryParameters: { 100 'limit': 50, 101 'sort': 'popular', 102 }, 103 ); 104 105 final response = await apiService.listCommunities(); 106 107 expect(response.communities, isEmpty); 108 }); 109 110 test('should fetch communities with custom limit', () async { 111 final mockResponse = { 112 'communities': [], 113 'cursor': null, 114 }; 115 116 dioAdapter.onGet( 117 '/xrpc/social.coves.community.list', 118 (server) => server.reply(200, mockResponse), 119 queryParameters: { 120 'limit': 25, 121 'sort': 'popular', 122 }, 123 ); 124 125 final response = await apiService.listCommunities(limit: 25); 126 127 expect(response, isA<CommunitiesResponse>()); 128 }); 129 130 test('should fetch communities with cursor for pagination', () async { 131 const cursor = 'pagination-cursor-123'; 132 133 final mockResponse = { 134 'communities': [ 135 { 136 'did': 'did:plc:community3', 137 'name': 'paginated-community', 138 }, 139 ], 140 'cursor': 'next-cursor-456', 141 }; 142 143 dioAdapter.onGet( 144 '/xrpc/social.coves.community.list', 145 (server) => server.reply(200, mockResponse), 146 queryParameters: { 147 'limit': 50, 148 'sort': 'popular', 149 'cursor': cursor, 150 }, 151 ); 152 153 final response = await apiService.listCommunities(cursor: cursor); 154 155 expect(response.communities.length, 1); 156 expect(response.cursor, 'next-cursor-456'); 157 }); 158 159 test('should fetch communities with custom sort', () async { 160 final mockResponse = { 161 'communities': [], 162 'cursor': null, 163 }; 164 165 dioAdapter.onGet( 166 '/xrpc/social.coves.community.list', 167 (server) => server.reply(200, mockResponse), 168 queryParameters: { 169 'limit': 50, 170 'sort': 'new', 171 }, 172 ); 173 174 final response = await apiService.listCommunities(sort: 'new'); 175 176 expect(response, isA<CommunitiesResponse>()); 177 }); 178 179 test('should handle 401 unauthorized error', () async { 180 dioAdapter.onGet( 181 '/xrpc/social.coves.community.list', 182 (server) => server.reply(401, { 183 'error': 'Unauthorized', 184 'message': 'Invalid token', 185 }), 186 queryParameters: { 187 'limit': 50, 188 'sort': 'popular', 189 }, 190 ); 191 192 expect( 193 () => apiService.listCommunities(), 194 throwsA(isA<AuthenticationException>()), 195 ); 196 }); 197 198 test('should handle 500 server error', () async { 199 dioAdapter.onGet( 200 '/xrpc/social.coves.community.list', 201 (server) => server.reply(500, { 202 'error': 'InternalServerError', 203 'message': 'Database error', 204 }), 205 queryParameters: { 206 'limit': 50, 207 'sort': 'popular', 208 }, 209 ); 210 211 expect( 212 () => apiService.listCommunities(), 213 throwsA(isA<ServerException>()), 214 ); 215 }); 216 217 test('should handle network timeout', () async { 218 dioAdapter.onGet( 219 '/xrpc/social.coves.community.list', 220 (server) => server.throws( 221 408, 222 DioException.connectionTimeout( 223 timeout: const Duration(seconds: 30), 224 requestOptions: RequestOptions(), 225 ), 226 ), 227 queryParameters: { 228 'limit': 50, 229 'sort': 'popular', 230 }, 231 ); 232 233 expect( 234 () => apiService.listCommunities(), 235 throwsA(isA<NetworkException>()), 236 ); 237 }); 238 }); 239 240 group('CovesApiService - createPost', () { 241 late Dio dio; 242 late DioAdapter dioAdapter; 243 late CovesApiService apiService; 244 245 setUp(() { 246 dio = Dio(BaseOptions(baseUrl: 'https://api.test.coves.social')); 247 dioAdapter = DioAdapter(dio: dio); 248 apiService = CovesApiService( 249 dio: dio, 250 tokenGetter: () async => 'test-token', 251 ); 252 }); 253 254 tearDown(() { 255 apiService.dispose(); 256 }); 257 258 test('should successfully create a post with all fields', () async { 259 final mockResponse = { 260 'uri': 'at://did:plc:user/social.coves.community.post/123', 261 'cid': 'bafyreicid123', 262 }; 263 264 dioAdapter.onPost( 265 '/xrpc/social.coves.community.post.create', 266 (server) => server.reply(200, mockResponse), 267 data: { 268 'community': 'did:plc:community1', 269 'title': 'Test Post Title', 270 'content': 'Test post content', 271 'embed': { 272 r'$type': 'social.coves.embed.external', 273 'external': { 274 'uri': 'https://example.com/article', 275 'title': 'Article Title', 276 }, 277 }, 278 'langs': ['en'], 279 'labels': { 280 'values': [ 281 {'val': 'nsfw'}, 282 ], 283 }, 284 }, 285 ); 286 287 final response = await apiService.createPost( 288 community: 'did:plc:community1', 289 title: 'Test Post Title', 290 content: 'Test post content', 291 embed: ExternalEmbedInput( 292 uri: 'https://example.com/article', 293 title: 'Article Title', 294 ), 295 langs: ['en'], 296 labels: const SelfLabels(values: [SelfLabel(val: 'nsfw')]), 297 ); 298 299 expect(response, isA<CreatePostResponse>()); 300 expect(response.uri, 'at://did:plc:user/social.coves.community.post/123'); 301 expect(response.cid, 'bafyreicid123'); 302 }); 303 304 test('should successfully create a minimal post', () async { 305 final mockResponse = { 306 'uri': 'at://did:plc:user/social.coves.community.post/456', 307 'cid': 'bafyreicid456', 308 }; 309 310 dioAdapter.onPost( 311 '/xrpc/social.coves.community.post.create', 312 (server) => server.reply(200, mockResponse), 313 data: { 314 'community': 'did:plc:community1', 315 'title': 'Just a title', 316 }, 317 ); 318 319 final response = await apiService.createPost( 320 community: 'did:plc:community1', 321 title: 'Just a title', 322 ); 323 324 expect(response, isA<CreatePostResponse>()); 325 expect(response.uri, 'at://did:plc:user/social.coves.community.post/456'); 326 }); 327 328 test('should successfully create a link post', () async { 329 final mockResponse = { 330 'uri': 'at://did:plc:user/social.coves.community.post/789', 331 'cid': 'bafyreicid789', 332 }; 333 334 dioAdapter.onPost( 335 '/xrpc/social.coves.community.post.create', 336 (server) => server.reply(200, mockResponse), 337 data: { 338 'community': 'did:plc:community1', 339 'embed': { 340 'uri': 'https://example.com/article', 341 }, 342 }, 343 ); 344 345 final response = await apiService.createPost( 346 community: 'did:plc:community1', 347 embed: ExternalEmbedInput(uri: 'https://example.com/article'), 348 ); 349 350 expect(response, isA<CreatePostResponse>()); 351 }); 352 353 test('should handle 401 unauthorized error', () async { 354 dioAdapter.onPost( 355 '/xrpc/social.coves.community.post.create', 356 (server) => server.reply(401, { 357 'error': 'Unauthorized', 358 'message': 'Authentication required', 359 }), 360 data: { 361 'community': 'did:plc:community1', 362 'title': 'Test', 363 }, 364 ); 365 366 expect( 367 () => apiService.createPost( 368 community: 'did:plc:community1', 369 title: 'Test', 370 ), 371 throwsA(isA<AuthenticationException>()), 372 ); 373 }); 374 375 test('should handle 404 community not found', () async { 376 dioAdapter.onPost( 377 '/xrpc/social.coves.community.post.create', 378 (server) => server.reply(404, { 379 'error': 'NotFound', 380 'message': 'Community not found', 381 }), 382 data: { 383 'community': 'did:plc:nonexistent', 384 'title': 'Test', 385 }, 386 ); 387 388 expect( 389 () => apiService.createPost( 390 community: 'did:plc:nonexistent', 391 title: 'Test', 392 ), 393 throwsA(isA<NotFoundException>()), 394 ); 395 }); 396 397 test('should handle 400 validation error', () async { 398 dioAdapter.onPost( 399 '/xrpc/social.coves.community.post.create', 400 (server) => server.reply(400, { 401 'error': 'ValidationError', 402 'message': 'Title exceeds maximum length', 403 }), 404 data: { 405 'community': 'did:plc:community1', 406 'title': 'a' * 1000, // Very long title 407 }, 408 ); 409 410 expect( 411 () => apiService.createPost( 412 community: 'did:plc:community1', 413 title: 'a' * 1000, 414 ), 415 throwsA(isA<ApiException>()), 416 ); 417 }); 418 419 test('should handle 500 server error', () async { 420 dioAdapter.onPost( 421 '/xrpc/social.coves.community.post.create', 422 (server) => server.reply(500, { 423 'error': 'InternalServerError', 424 'message': 'Database error', 425 }), 426 data: { 427 'community': 'did:plc:community1', 428 'title': 'Test', 429 }, 430 ); 431 432 expect( 433 () => apiService.createPost( 434 community: 'did:plc:community1', 435 title: 'Test', 436 ), 437 throwsA(isA<ServerException>()), 438 ); 439 }); 440 441 test('should handle network timeout', () async { 442 dioAdapter.onPost( 443 '/xrpc/social.coves.community.post.create', 444 (server) => server.throws( 445 408, 446 DioException.connectionTimeout( 447 timeout: const Duration(seconds: 30), 448 requestOptions: RequestOptions(), 449 ), 450 ), 451 data: { 452 'community': 'did:plc:community1', 453 'title': 'Test', 454 }, 455 ); 456 457 expect( 458 () => apiService.createPost( 459 community: 'did:plc:community1', 460 title: 'Test', 461 ), 462 throwsA(isA<NetworkException>()), 463 ); 464 }); 465 }); 466}