From 26007d122df7a9e36e889f99e35a21a135e44508 Mon Sep 17 00:00:00 2001 From: Bretton Date: Thu, 4 Dec 2025 15:53:21 -0800 Subject: [PATCH] test: add tests for CommentService and CommentsProvider.createComment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CommentService tests (10 tests): - Successful comment creation - Auth failure when no session - Network error handling - 401 response handling - Invalid responses (null data, missing uri, empty uri) - Server error handling - Nested reply request format CommentsProvider.createComment tests (12 tests): - Validation: empty content, whitespace-only, exceeds limit - Emoji counting with grapheme clusters - Error states: no post loaded, no CommentService - Top-level comment (reply to post) - Nested comment (reply to comment) - Content trimming - Refresh after success - Exception propagation - Boundary testing at max length All 52 tests pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- test/providers/comments_provider_test.dart | 455 +++++++++- .../comments_provider_test.mocks.dart | 135 ++- test/services/comment_service_test.dart | 357 ++++++++ test/services/comment_service_test.mocks.dart | 806 ++++++++++++++++++ 4 files changed, 1701 insertions(+), 52 deletions(-) create mode 100644 test/services/comment_service_test.dart create mode 100644 test/services/comment_service_test.mocks.dart diff --git a/test/providers/comments_provider_test.dart b/test/providers/comments_provider_test.dart index 257bde1..ce3b547 100644 --- a/test/providers/comments_provider_test.dart +++ b/test/providers/comments_provider_test.dart @@ -3,6 +3,8 @@ import 'package:coves_flutter/models/post.dart'; import 'package:coves_flutter/providers/auth_provider.dart'; import 'package:coves_flutter/providers/comments_provider.dart'; import 'package:coves_flutter/providers/vote_provider.dart'; +import 'package:coves_flutter/services/api_exceptions.dart'; +import 'package:coves_flutter/services/comment_service.dart'; import 'package:coves_flutter/services/coves_api_service.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; @@ -11,11 +13,14 @@ import 'package:mockito/mockito.dart'; import 'comments_provider_test.mocks.dart'; // Generate mocks for dependencies -@GenerateMocks([AuthProvider, CovesApiService, VoteProvider]) +@GenerateMocks([AuthProvider, CovesApiService, VoteProvider, CommentService]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('CommentsProvider', () { + const testPostUri = 'at://did:plc:test/social.coves.post.record/123'; + const testPostCid = 'test-post-cid'; + late CommentsProvider commentsProvider; late MockAuthProvider mockAuthProvider; late MockCovesApiService mockApiService; @@ -44,8 +49,6 @@ void main() { }); group('loadComments', () { - const testPostUri = 'at://did:plc:test/social.coves.post.record/123'; - test('should load comments successfully', () async { final mockComments = [ _createMockThreadComment('comment1'), @@ -71,6 +74,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -96,6 +100,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -118,6 +123,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -141,6 +147,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -169,6 +176,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -192,7 +200,7 @@ void main() { ), ).thenAnswer((_) async => secondResponse); - await commentsProvider.loadComments(postUri: testPostUri); + await commentsProvider.loadComments(postUri: testPostUri, postCid: testPostCid); expect(commentsProvider.comments.length, 2); expect(commentsProvider.comments[0].comment.uri, 'comment1'); @@ -220,6 +228,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -247,6 +256,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -274,6 +284,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -301,6 +312,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -309,6 +321,7 @@ void main() { // Load different post const differentPostUri = 'at://did:plc:test/social.coves.post.record/456'; + const differentPostCid = 'different-post-cid'; final secondResponse = CommentsResponse( post: {}, comments: [_createMockThreadComment('comment2')], @@ -327,6 +340,7 @@ void main() { await commentsProvider.loadComments( postUri: differentPostUri, + postCid: differentPostCid, refresh: true, ); @@ -359,12 +373,14 @@ void main() { // Start first load final firstFuture = commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); // Try to load again while still loading - should schedule a refresh await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -408,6 +424,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -437,6 +454,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -467,6 +485,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -524,6 +543,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -566,6 +586,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -635,6 +656,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -682,6 +704,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -737,6 +760,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -788,6 +812,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -823,7 +848,8 @@ void main() { expect(commentsProvider.currentTimeNotifier.value, null); await commentsProvider.loadComments( - postUri: 'at://did:plc:test/social.coves.post.record/123', + postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -848,7 +874,8 @@ void main() { ).thenAnswer((_) async => response); await commentsProvider.loadComments( - postUri: 'at://did:plc:test/social.coves.post.record/123', + postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -886,7 +913,8 @@ void main() { ).thenAnswer((_) async => response); await commentsProvider.loadComments( - postUri: 'at://did:plc:test/social.coves.post.record/123', + postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -914,7 +942,8 @@ void main() { }); final loadFuture = commentsProvider.loadComments( - postUri: 'at://did:plc:test/social.coves.post.record/123', + postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -956,6 +985,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -993,6 +1023,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -1032,6 +1063,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -1081,6 +1113,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -1143,6 +1176,7 @@ void main() { await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -1211,6 +1245,7 @@ void main() { // Load first page (refresh) await commentsProvider.loadComments( postUri: testPostUri, + postCid: testPostCid, refresh: true, ); @@ -1250,6 +1285,410 @@ void main() { }, ); }); + + group('createComment', () { + late MockCommentService mockCommentService; + late CommentsProvider providerWithCommentService; + + setUp(() { + mockCommentService = MockCommentService(); + + // Setup mock API service for loadComments + final mockResponse = CommentsResponse( + post: {}, + comments: [_createMockThreadComment('comment1')], + ); + when( + mockApiService.getComments( + postUri: anyNamed('postUri'), + sort: anyNamed('sort'), + timeframe: anyNamed('timeframe'), + depth: anyNamed('depth'), + limit: anyNamed('limit'), + cursor: anyNamed('cursor'), + ), + ).thenAnswer((_) async => mockResponse); + + providerWithCommentService = CommentsProvider( + mockAuthProvider, + apiService: mockApiService, + voteProvider: mockVoteProvider, + commentService: mockCommentService, + ); + }); + + tearDown(() { + providerWithCommentService.dispose(); + }); + + test('should throw ValidationException for empty content', () async { + // First load comments to set up post context + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + expect( + () => providerWithCommentService.createComment(content: ''), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('empty'), + ), + ), + ); + }); + + test('should throw ValidationException for whitespace-only content', () async { + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + expect( + () => providerWithCommentService.createComment(content: ' \n\t '), + throwsA(isA()), + ); + }); + + test('should throw ValidationException for content exceeding limit', () async { + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + // Create a string longer than 10000 characters + final longContent = 'a' * 10001; + + expect( + () => providerWithCommentService.createComment(content: longContent), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('too long'), + ), + ), + ); + }); + + test('should count emoji correctly in character limit', () async { + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + // Each emoji should count as 1 character, not 2-4 bytes + // 9999 'a' chars + 1 emoji = 10000 chars (should pass) + final contentAtLimit = '${'a' * 9999}😀'; + + when( + mockCommentService.createComment( + rootUri: anyNamed('rootUri'), + rootCid: anyNamed('rootCid'), + parentUri: anyNamed('parentUri'), + parentCid: anyNamed('parentCid'), + content: anyNamed('content'), + ), + ).thenAnswer( + (_) async => const CreateCommentResponse( + uri: 'at://did:plc:test/comment/abc', + cid: 'cid123', + ), + ); + + // This should NOT throw + await providerWithCommentService.createComment(content: contentAtLimit); + + verify( + mockCommentService.createComment( + rootUri: testPostUri, + rootCid: testPostCid, + parentUri: testPostUri, + parentCid: testPostCid, + content: contentAtLimit, + ), + ).called(1); + }); + + test('should throw ApiException when no post loaded', () async { + // Don't call loadComments first - no post context + + expect( + () => providerWithCommentService.createComment( + content: 'Test comment', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('No post loaded'), + ), + ), + ); + }); + + test('should throw ApiException when no CommentService', () async { + // Create provider without CommentService + final providerWithoutService = CommentsProvider( + mockAuthProvider, + apiService: mockApiService, + voteProvider: mockVoteProvider, + ); + + await providerWithoutService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + expect( + () => providerWithoutService.createComment(content: 'Test comment'), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('CommentService not available'), + ), + ), + ); + + providerWithoutService.dispose(); + }); + + test('should create top-level comment (reply to post)', () async { + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + when( + mockCommentService.createComment( + rootUri: anyNamed('rootUri'), + rootCid: anyNamed('rootCid'), + parentUri: anyNamed('parentUri'), + parentCid: anyNamed('parentCid'), + content: anyNamed('content'), + ), + ).thenAnswer( + (_) async => const CreateCommentResponse( + uri: 'at://did:plc:test/comment/abc', + cid: 'cid123', + ), + ); + + await providerWithCommentService.createComment( + content: 'This is a test comment', + ); + + // Verify the comment service was called with correct parameters + // Root and parent should both be the post for top-level comments + verify( + mockCommentService.createComment( + rootUri: testPostUri, + rootCid: testPostCid, + parentUri: testPostUri, + parentCid: testPostCid, + content: 'This is a test comment', + ), + ).called(1); + }); + + test('should create nested comment (reply to comment)', () async { + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + when( + mockCommentService.createComment( + rootUri: anyNamed('rootUri'), + rootCid: anyNamed('rootCid'), + parentUri: anyNamed('parentUri'), + parentCid: anyNamed('parentCid'), + content: anyNamed('content'), + ), + ).thenAnswer( + (_) async => const CreateCommentResponse( + uri: 'at://did:plc:test/comment/reply1', + cid: 'cidReply', + ), + ); + + // Create a parent comment to reply to + final parentComment = _createMockThreadComment('parent-comment'); + + await providerWithCommentService.createComment( + content: 'This is a nested reply', + parentComment: parentComment, + ); + + // Root should still be the post, but parent should be the comment + verify( + mockCommentService.createComment( + rootUri: testPostUri, + rootCid: testPostCid, + parentUri: 'parent-comment', + parentCid: 'cid-parent-comment', + content: 'This is a nested reply', + ), + ).called(1); + }); + + test('should trim content before sending', () async { + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + when( + mockCommentService.createComment( + rootUri: anyNamed('rootUri'), + rootCid: anyNamed('rootCid'), + parentUri: anyNamed('parentUri'), + parentCid: anyNamed('parentCid'), + content: anyNamed('content'), + ), + ).thenAnswer( + (_) async => const CreateCommentResponse( + uri: 'at://did:plc:test/comment/abc', + cid: 'cid123', + ), + ); + + await providerWithCommentService.createComment( + content: ' Hello world! ', + ); + + // Verify trimmed content was sent + verify( + mockCommentService.createComment( + rootUri: anyNamed('rootUri'), + rootCid: anyNamed('rootCid'), + parentUri: anyNamed('parentUri'), + parentCid: anyNamed('parentCid'), + content: 'Hello world!', + ), + ).called(1); + }); + + test('should refresh comments after successful creation', () async { + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + when( + mockCommentService.createComment( + rootUri: anyNamed('rootUri'), + rootCid: anyNamed('rootCid'), + parentUri: anyNamed('parentUri'), + parentCid: anyNamed('parentCid'), + content: anyNamed('content'), + ), + ).thenAnswer( + (_) async => const CreateCommentResponse( + uri: 'at://did:plc:test/comment/abc', + cid: 'cid123', + ), + ); + + await providerWithCommentService.createComment( + content: 'Test comment', + ); + + // Should have called getComments twice - once for initial load, + // once for refresh after comment creation + verify( + mockApiService.getComments( + postUri: anyNamed('postUri'), + sort: anyNamed('sort'), + timeframe: anyNamed('timeframe'), + depth: anyNamed('depth'), + limit: anyNamed('limit'), + cursor: anyNamed('cursor'), + ), + ).called(2); + }); + + test('should rethrow exception from CommentService', () async { + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + when( + mockCommentService.createComment( + rootUri: anyNamed('rootUri'), + rootCid: anyNamed('rootCid'), + parentUri: anyNamed('parentUri'), + parentCid: anyNamed('parentCid'), + content: anyNamed('content'), + ), + ).thenThrow(ApiException('Network error')); + + expect( + () => providerWithCommentService.createComment( + content: 'Test comment', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('Network error'), + ), + ), + ); + }); + + test('should accept content at exactly max length', () async { + await providerWithCommentService.loadComments( + postUri: testPostUri, + postCid: testPostCid, + refresh: true, + ); + + final contentAtLimit = 'a' * CommentsProvider.maxCommentLength; + + when( + mockCommentService.createComment( + rootUri: anyNamed('rootUri'), + rootCid: anyNamed('rootCid'), + parentUri: anyNamed('parentUri'), + parentCid: anyNamed('parentCid'), + content: anyNamed('content'), + ), + ).thenAnswer( + (_) async => const CreateCommentResponse( + uri: 'at://did:plc:test/comment/abc', + cid: 'cid123', + ), + ); + + // Should not throw + await providerWithCommentService.createComment(content: contentAtLimit); + + verify( + mockCommentService.createComment( + rootUri: anyNamed('rootUri'), + rootCid: anyNamed('rootCid'), + parentUri: anyNamed('parentUri'), + parentCid: anyNamed('parentCid'), + content: contentAtLimit, + ), + ).called(1); + }); + }); }); } diff --git a/test/providers/comments_provider_test.mocks.dart b/test/providers/comments_provider_test.mocks.dart index 55b5ef3..e43e655 100644 --- a/test/providers/comments_provider_test.mocks.dart +++ b/test/providers/comments_provider_test.mocks.dart @@ -3,14 +3,15 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i5; -import 'dart:ui' as _i6; +import 'dart:async' as _i6; +import 'dart:ui' as _i7; import 'package:coves_flutter/models/comment.dart' as _i3; import 'package:coves_flutter/models/post.dart' as _i2; -import 'package:coves_flutter/providers/auth_provider.dart' as _i4; -import 'package:coves_flutter/providers/vote_provider.dart' as _i8; -import 'package:coves_flutter/services/coves_api_service.dart' as _i7; +import 'package:coves_flutter/providers/auth_provider.dart' as _i5; +import 'package:coves_flutter/providers/vote_provider.dart' as _i9; +import 'package:coves_flutter/services/comment_service.dart' as _i4; +import 'package:coves_flutter/services/coves_api_service.dart' as _i8; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint @@ -40,10 +41,16 @@ class _FakeCommentsResponse_1 extends _i1.SmartFake : super(parent, parentInvocation); } +class _FakeCreateCommentResponse_2 extends _i1.SmartFake + implements _i4.CreateCommentResponse { + _FakeCreateCommentResponse_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + /// A class which mocks [AuthProvider]. /// /// See the documentation for Mockito's code generation for more information. -class MockAuthProvider extends _i1.Mock implements _i4.AuthProvider { +class MockAuthProvider extends _i1.Mock implements _i5.AuthProvider { MockAuthProvider() { _i1.throwOnMissingStub(this); } @@ -67,47 +74,47 @@ class MockAuthProvider extends _i1.Mock implements _i4.AuthProvider { as bool); @override - _i5.Future getAccessToken() => + _i6.Future getAccessToken() => (super.noSuchMethod( Invocation.method(#getAccessToken, []), - returnValue: _i5.Future.value(), + returnValue: _i6.Future.value(), ) - as _i5.Future); + as _i6.Future); @override - _i5.Future initialize() => + _i6.Future initialize() => (super.noSuchMethod( Invocation.method(#initialize, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), ) - as _i5.Future); + as _i6.Future); @override - _i5.Future signIn(String? handle) => + _i6.Future signIn(String? handle) => (super.noSuchMethod( Invocation.method(#signIn, [handle]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), ) - as _i5.Future); + as _i6.Future); @override - _i5.Future signOut() => + _i6.Future signOut() => (super.noSuchMethod( Invocation.method(#signOut, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), ) - as _i5.Future); + as _i6.Future); @override - _i5.Future refreshToken() => + _i6.Future refreshToken() => (super.noSuchMethod( Invocation.method(#refreshToken, []), - returnValue: _i5.Future.value(false), + returnValue: _i6.Future.value(false), ) - as _i5.Future); + as _i6.Future); @override void clearError() => super.noSuchMethod( @@ -116,13 +123,13 @@ class MockAuthProvider extends _i1.Mock implements _i4.AuthProvider { ); @override - void addListener(_i6.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i6.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); @@ -143,13 +150,13 @@ class MockAuthProvider extends _i1.Mock implements _i4.AuthProvider { /// A class which mocks [CovesApiService]. /// /// See the documentation for Mockito's code generation for more information. -class MockCovesApiService extends _i1.Mock implements _i7.CovesApiService { +class MockCovesApiService extends _i1.Mock implements _i8.CovesApiService { MockCovesApiService() { _i1.throwOnMissingStub(this); } @override - _i5.Future<_i2.TimelineResponse> getTimeline({ + _i6.Future<_i2.TimelineResponse> getTimeline({ String? sort = 'hot', String? timeframe, int? limit = 15, @@ -162,7 +169,7 @@ class MockCovesApiService extends _i1.Mock implements _i7.CovesApiService { #limit: limit, #cursor: cursor, }), - returnValue: _i5.Future<_i2.TimelineResponse>.value( + returnValue: _i6.Future<_i2.TimelineResponse>.value( _FakeTimelineResponse_0( this, Invocation.method(#getTimeline, [], { @@ -174,10 +181,10 @@ class MockCovesApiService extends _i1.Mock implements _i7.CovesApiService { ), ), ) - as _i5.Future<_i2.TimelineResponse>); + as _i6.Future<_i2.TimelineResponse>); @override - _i5.Future<_i2.TimelineResponse> getDiscover({ + _i6.Future<_i2.TimelineResponse> getDiscover({ String? sort = 'hot', String? timeframe, int? limit = 15, @@ -190,7 +197,7 @@ class MockCovesApiService extends _i1.Mock implements _i7.CovesApiService { #limit: limit, #cursor: cursor, }), - returnValue: _i5.Future<_i2.TimelineResponse>.value( + returnValue: _i6.Future<_i2.TimelineResponse>.value( _FakeTimelineResponse_0( this, Invocation.method(#getDiscover, [], { @@ -202,10 +209,10 @@ class MockCovesApiService extends _i1.Mock implements _i7.CovesApiService { ), ), ) - as _i5.Future<_i2.TimelineResponse>); + as _i6.Future<_i2.TimelineResponse>); @override - _i5.Future<_i3.CommentsResponse> getComments({ + _i6.Future<_i3.CommentsResponse> getComments({ required String? postUri, String? sort = 'hot', String? timeframe, @@ -222,7 +229,7 @@ class MockCovesApiService extends _i1.Mock implements _i7.CovesApiService { #limit: limit, #cursor: cursor, }), - returnValue: _i5.Future<_i3.CommentsResponse>.value( + returnValue: _i6.Future<_i3.CommentsResponse>.value( _FakeCommentsResponse_1( this, Invocation.method(#getComments, [], { @@ -236,7 +243,7 @@ class MockCovesApiService extends _i1.Mock implements _i7.CovesApiService { ), ), ) - as _i5.Future<_i3.CommentsResponse>); + as _i6.Future<_i3.CommentsResponse>); @override void dispose() => super.noSuchMethod( @@ -248,7 +255,7 @@ class MockCovesApiService extends _i1.Mock implements _i7.CovesApiService { /// A class which mocks [VoteProvider]. /// /// See the documentation for Mockito's code generation for more information. -class MockVoteProvider extends _i1.Mock implements _i8.VoteProvider { +class MockVoteProvider extends _i1.Mock implements _i9.VoteProvider { MockVoteProvider() { _i1.throwOnMissingStub(this); } @@ -265,9 +272,9 @@ class MockVoteProvider extends _i1.Mock implements _i8.VoteProvider { ); @override - _i8.VoteState? getVoteState(String? postUri) => + _i9.VoteState? getVoteState(String? postUri) => (super.noSuchMethod(Invocation.method(#getVoteState, [postUri])) - as _i8.VoteState?); + as _i9.VoteState?); @override bool isLiked(String? postUri) => @@ -294,7 +301,7 @@ class MockVoteProvider extends _i1.Mock implements _i8.VoteProvider { as int); @override - _i5.Future toggleVote({ + _i6.Future toggleVote({ required String? postUri, required String? postCid, String? direction = 'up', @@ -305,9 +312,9 @@ class MockVoteProvider extends _i1.Mock implements _i8.VoteProvider { #postCid: postCid, #direction: direction, }), - returnValue: _i5.Future.value(false), + returnValue: _i6.Future.value(false), ) - as _i5.Future); + as _i6.Future); @override void setInitialVoteState({ @@ -330,13 +337,13 @@ class MockVoteProvider extends _i1.Mock implements _i8.VoteProvider { ); @override - void addListener(_i6.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i6.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); @@ -347,3 +354,43 @@ class MockVoteProvider extends _i1.Mock implements _i8.VoteProvider { returnValueForMissingStub: null, ); } + +/// A class which mocks [CommentService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCommentService extends _i1.Mock implements _i4.CommentService { + MockCommentService() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.Future<_i4.CreateCommentResponse> createComment({ + required String? rootUri, + required String? rootCid, + required String? parentUri, + required String? parentCid, + required String? content, + }) => + (super.noSuchMethod( + Invocation.method(#createComment, [], { + #rootUri: rootUri, + #rootCid: rootCid, + #parentUri: parentUri, + #parentCid: parentCid, + #content: content, + }), + returnValue: _i6.Future<_i4.CreateCommentResponse>.value( + _FakeCreateCommentResponse_2( + this, + Invocation.method(#createComment, [], { + #rootUri: rootUri, + #rootCid: rootCid, + #parentUri: parentUri, + #parentCid: parentCid, + #content: content, + }), + ), + ), + ) + as _i6.Future<_i4.CreateCommentResponse>); +} diff --git a/test/services/comment_service_test.dart b/test/services/comment_service_test.dart new file mode 100644 index 0000000..743852c --- /dev/null +++ b/test/services/comment_service_test.dart @@ -0,0 +1,357 @@ +import 'package:coves_flutter/models/coves_session.dart'; +import 'package:coves_flutter/services/api_exceptions.dart'; +import 'package:coves_flutter/services/comment_service.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'comment_service_test.mocks.dart'; + +@GenerateMocks([Dio]) +void main() { + group('CommentService', () { + group('CreateCommentResponse', () { + test('should create response with uri and cid', () { + const response = CreateCommentResponse( + uri: 'at://did:plc:test/social.coves.community.comment/123', + cid: 'bafy123', + ); + + expect( + response.uri, + 'at://did:plc:test/social.coves.community.comment/123', + ); + expect(response.cid, 'bafy123'); + }); + }); + + group('createComment', () { + late MockDio mockDio; + late CommentService commentService; + late CovesSession testSession; + + setUp(() { + mockDio = MockDio(); + testSession = CovesSession( + token: 'test-token', + did: 'did:plc:test', + sessionId: 'test-session-id', + handle: 'test.user', + ); + + // Setup default interceptors behavior + when(mockDio.interceptors).thenReturn(Interceptors()); + + commentService = CommentService( + sessionGetter: () async => testSession, + tokenRefresher: () async => true, + signOutHandler: () async {}, + dio: mockDio, + ); + }); + + test('should create comment successfully', () async { + when( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: anyNamed('data'), + ), + ).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(path: ''), + statusCode: 200, + data: { + 'uri': 'at://did:plc:test/social.coves.community.comment/abc123', + 'cid': 'bafy123', + }, + ), + ); + + final response = await commentService.createComment( + rootUri: 'at://did:plc:author/social.coves.post.record/post123', + rootCid: 'rootCid123', + parentUri: 'at://did:plc:author/social.coves.post.record/post123', + parentCid: 'parentCid123', + content: 'This is a test comment', + ); + + expect( + response.uri, + 'at://did:plc:test/social.coves.community.comment/abc123', + ); + expect(response.cid, 'bafy123'); + + verify( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: { + 'reply': { + 'root': { + 'uri': 'at://did:plc:author/social.coves.post.record/post123', + 'cid': 'rootCid123', + }, + 'parent': { + 'uri': 'at://did:plc:author/social.coves.post.record/post123', + 'cid': 'parentCid123', + }, + }, + 'content': 'This is a test comment', + }, + ), + ).called(1); + }); + + test('should throw AuthenticationException when no session', () async { + final serviceWithoutSession = CommentService( + sessionGetter: () async => null, + tokenRefresher: () async => true, + signOutHandler: () async {}, + dio: mockDio, + ); + + expect( + () => serviceWithoutSession.createComment( + rootUri: 'at://did:plc:author/post/123', + rootCid: 'rootCid', + parentUri: 'at://did:plc:author/post/123', + parentCid: 'parentCid', + content: 'Test comment', + ), + throwsA(isA()), + ); + }); + + test('should throw ApiException on network error', () async { + when( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: anyNamed('data'), + ), + ).thenThrow( + DioException( + requestOptions: RequestOptions(path: ''), + type: DioExceptionType.connectionError, + message: 'Connection failed', + ), + ); + + expect( + () => commentService.createComment( + rootUri: 'at://did:plc:author/post/123', + rootCid: 'rootCid', + parentUri: 'at://did:plc:author/post/123', + parentCid: 'parentCid', + content: 'Test comment', + ), + throwsA(isA()), + ); + }); + + test('should throw AuthenticationException on 401 response', () async { + when( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: anyNamed('data'), + ), + ).thenThrow( + DioException( + requestOptions: RequestOptions(path: ''), + type: DioExceptionType.badResponse, + response: Response( + requestOptions: RequestOptions(path: ''), + statusCode: 401, + data: {'error': 'Unauthorized'}, + ), + ), + ); + + expect( + () => commentService.createComment( + rootUri: 'at://did:plc:author/post/123', + rootCid: 'rootCid', + parentUri: 'at://did:plc:author/post/123', + parentCid: 'parentCid', + content: 'Test comment', + ), + throwsA(isA()), + ); + }); + + test('should throw ApiException on invalid response (null data)', () async { + when( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: anyNamed('data'), + ), + ).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(path: ''), + statusCode: 200, + data: null, + ), + ); + + expect( + () => commentService.createComment( + rootUri: 'at://did:plc:author/post/123', + rootCid: 'rootCid', + parentUri: 'at://did:plc:author/post/123', + parentCid: 'parentCid', + content: 'Test comment', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('no data'), + ), + ), + ); + }); + + test('should throw ApiException on invalid response (missing uri)', () async { + when( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: anyNamed('data'), + ), + ).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(path: ''), + statusCode: 200, + data: {'cid': 'bafy123'}, + ), + ); + + expect( + () => commentService.createComment( + rootUri: 'at://did:plc:author/post/123', + rootCid: 'rootCid', + parentUri: 'at://did:plc:author/post/123', + parentCid: 'parentCid', + content: 'Test comment', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('missing uri'), + ), + ), + ); + }); + + test('should throw ApiException on invalid response (empty uri)', () async { + when( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: anyNamed('data'), + ), + ).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(path: ''), + statusCode: 200, + data: {'uri': '', 'cid': 'bafy123'}, + ), + ); + + expect( + () => commentService.createComment( + rootUri: 'at://did:plc:author/post/123', + rootCid: 'rootCid', + parentUri: 'at://did:plc:author/post/123', + parentCid: 'parentCid', + content: 'Test comment', + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('missing uri'), + ), + ), + ); + }); + + test('should throw ApiException on server error', () async { + when( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: anyNamed('data'), + ), + ).thenThrow( + DioException( + requestOptions: RequestOptions(path: ''), + type: DioExceptionType.badResponse, + response: Response( + requestOptions: RequestOptions(path: ''), + statusCode: 500, + data: {'error': 'Internal server error'}, + ), + message: 'Internal server error', + ), + ); + + expect( + () => commentService.createComment( + rootUri: 'at://did:plc:author/post/123', + rootCid: 'rootCid', + parentUri: 'at://did:plc:author/post/123', + parentCid: 'parentCid', + content: 'Test comment', + ), + throwsA(isA()), + ); + }); + + test('should send correct parent for nested reply', () async { + when( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: anyNamed('data'), + ), + ).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(path: ''), + statusCode: 200, + data: { + 'uri': 'at://did:plc:test/social.coves.community.comment/reply1', + 'cid': 'bafyReply', + }, + ), + ); + + await commentService.createComment( + rootUri: 'at://did:plc:author/social.coves.post.record/post123', + rootCid: 'postCid', + parentUri: + 'at://did:plc:commenter/social.coves.community.comment/comment1', + parentCid: 'commentCid', + content: 'This is a nested reply', + ); + + verify( + mockDio.post>( + '/xrpc/social.coves.community.comment.create', + data: { + 'reply': { + 'root': { + 'uri': 'at://did:plc:author/social.coves.post.record/post123', + 'cid': 'postCid', + }, + 'parent': { + 'uri': + 'at://did:plc:commenter/social.coves.community.comment/' + 'comment1', + 'cid': 'commentCid', + }, + }, + 'content': 'This is a nested reply', + }, + ), + ).called(1); + }); + }); + }); +} diff --git a/test/services/comment_service_test.mocks.dart b/test/services/comment_service_test.mocks.dart new file mode 100644 index 0000000..3c34efb --- /dev/null +++ b/test/services/comment_service_test.mocks.dart @@ -0,0 +1,806 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in coves_flutter/test/services/comment_service_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i8; + +import 'package:dio/src/adapter.dart' as _i4; +import 'package:dio/src/cancel_token.dart' as _i9; +import 'package:dio/src/dio.dart' as _i7; +import 'package:dio/src/dio_mixin.dart' as _i3; +import 'package:dio/src/options.dart' as _i2; +import 'package:dio/src/response.dart' as _i6; +import 'package:dio/src/transformer.dart' as _i5; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member + +class _FakeBaseOptions_0 extends _i1.SmartFake implements _i2.BaseOptions { + _FakeBaseOptions_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeInterceptors_1 extends _i1.SmartFake implements _i3.Interceptors { + _FakeInterceptors_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeHttpClientAdapter_2 extends _i1.SmartFake + implements _i4.HttpClientAdapter { + _FakeHttpClientAdapter_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeTransformer_3 extends _i1.SmartFake implements _i5.Transformer { + _FakeTransformer_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeResponse_4 extends _i1.SmartFake implements _i6.Response { + _FakeResponse_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeDio_5 extends _i1.SmartFake implements _i7.Dio { + _FakeDio_5(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [Dio]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDio extends _i1.Mock implements _i7.Dio { + MockDio() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.BaseOptions get options => + (super.noSuchMethod( + Invocation.getter(#options), + returnValue: _FakeBaseOptions_0(this, Invocation.getter(#options)), + ) + as _i2.BaseOptions); + + @override + _i3.Interceptors get interceptors => + (super.noSuchMethod( + Invocation.getter(#interceptors), + returnValue: _FakeInterceptors_1( + this, + Invocation.getter(#interceptors), + ), + ) + as _i3.Interceptors); + + @override + _i4.HttpClientAdapter get httpClientAdapter => + (super.noSuchMethod( + Invocation.getter(#httpClientAdapter), + returnValue: _FakeHttpClientAdapter_2( + this, + Invocation.getter(#httpClientAdapter), + ), + ) + as _i4.HttpClientAdapter); + + @override + _i5.Transformer get transformer => + (super.noSuchMethod( + Invocation.getter(#transformer), + returnValue: _FakeTransformer_3( + this, + Invocation.getter(#transformer), + ), + ) + as _i5.Transformer); + + @override + set options(_i2.BaseOptions? value) => super.noSuchMethod( + Invocation.setter(#options, value), + returnValueForMissingStub: null, + ); + + @override + set httpClientAdapter(_i4.HttpClientAdapter? value) => super.noSuchMethod( + Invocation.setter(#httpClientAdapter, value), + returnValueForMissingStub: null, + ); + + @override + set transformer(_i5.Transformer? value) => super.noSuchMethod( + Invocation.setter(#transformer, value), + returnValueForMissingStub: null, + ); + + @override + void close({bool? force = false}) => super.noSuchMethod( + Invocation.method(#close, [], {#force: force}), + returnValueForMissingStub: null, + ); + + @override + _i8.Future<_i6.Response> head( + String? path, { + Object? data, + Map? queryParameters, + _i2.Options? options, + _i9.CancelToken? cancelToken, + }) => + (super.noSuchMethod( + Invocation.method( + #head, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #head, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> headUri( + Uri? uri, { + Object? data, + _i2.Options? options, + _i9.CancelToken? cancelToken, + }) => + (super.noSuchMethod( + Invocation.method( + #headUri, + [uri], + {#data: data, #options: options, #cancelToken: cancelToken}, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #headUri, + [uri], + {#data: data, #options: options, #cancelToken: cancelToken}, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> get( + String? path, { + Object? data, + Map? queryParameters, + _i2.Options? options, + _i9.CancelToken? cancelToken, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #get, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> getUri( + Uri? uri, { + Object? data, + _i2.Options? options, + _i9.CancelToken? cancelToken, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #getUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #getUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> post( + String? path, { + Object? data, + Map? queryParameters, + _i2.Options? options, + _i9.CancelToken? cancelToken, + _i2.ProgressCallback? onSendProgress, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #post, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> postUri( + Uri? uri, { + Object? data, + _i2.Options? options, + _i9.CancelToken? cancelToken, + _i2.ProgressCallback? onSendProgress, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #postUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #postUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> put( + String? path, { + Object? data, + Map? queryParameters, + _i2.Options? options, + _i9.CancelToken? cancelToken, + _i2.ProgressCallback? onSendProgress, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #put, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #put, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> putUri( + Uri? uri, { + Object? data, + _i2.Options? options, + _i9.CancelToken? cancelToken, + _i2.ProgressCallback? onSendProgress, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #putUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #putUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> patch( + String? path, { + Object? data, + Map? queryParameters, + _i2.Options? options, + _i9.CancelToken? cancelToken, + _i2.ProgressCallback? onSendProgress, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #patch, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #patch, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> patchUri( + Uri? uri, { + Object? data, + _i2.Options? options, + _i9.CancelToken? cancelToken, + _i2.ProgressCallback? onSendProgress, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #patchUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #patchUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> delete( + String? path, { + Object? data, + Map? queryParameters, + _i2.Options? options, + _i9.CancelToken? cancelToken, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #delete, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> deleteUri( + Uri? uri, { + Object? data, + _i2.Options? options, + _i9.CancelToken? cancelToken, + }) => + (super.noSuchMethod( + Invocation.method( + #deleteUri, + [uri], + {#data: data, #options: options, #cancelToken: cancelToken}, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #deleteUri, + [uri], + {#data: data, #options: options, #cancelToken: cancelToken}, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> download( + String? urlPath, + dynamic savePath, { + _i2.ProgressCallback? onReceiveProgress, + Map? queryParameters, + _i9.CancelToken? cancelToken, + bool? deleteOnError = true, + _i2.FileAccessMode? fileAccessMode = _i2.FileAccessMode.write, + String? lengthHeader = 'content-length', + Object? data, + _i2.Options? options, + }) => + (super.noSuchMethod( + Invocation.method( + #download, + [urlPath, savePath], + { + #onReceiveProgress: onReceiveProgress, + #queryParameters: queryParameters, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #download, + [urlPath, savePath], + { + #onReceiveProgress: onReceiveProgress, + #queryParameters: queryParameters, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> downloadUri( + Uri? uri, + dynamic savePath, { + _i2.ProgressCallback? onReceiveProgress, + _i9.CancelToken? cancelToken, + bool? deleteOnError = true, + _i2.FileAccessMode? fileAccessMode = _i2.FileAccessMode.write, + String? lengthHeader = 'content-length', + Object? data, + _i2.Options? options, + }) => + (super.noSuchMethod( + Invocation.method( + #downloadUri, + [uri, savePath], + { + #onReceiveProgress: onReceiveProgress, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #downloadUri, + [uri, savePath], + { + #onReceiveProgress: onReceiveProgress, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> request( + String? url, { + Object? data, + Map? queryParameters, + _i9.CancelToken? cancelToken, + _i2.Options? options, + _i2.ProgressCallback? onSendProgress, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #request, + [url], + { + #data: data, + #queryParameters: queryParameters, + #cancelToken: cancelToken, + #options: options, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #request, + [url], + { + #data: data, + #queryParameters: queryParameters, + #cancelToken: cancelToken, + #options: options, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> requestUri( + Uri? uri, { + Object? data, + _i9.CancelToken? cancelToken, + _i2.Options? options, + _i2.ProgressCallback? onSendProgress, + _i2.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #requestUri, + [uri], + { + #data: data, + #cancelToken: cancelToken, + #options: options, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method( + #requestUri, + [uri], + { + #data: data, + #cancelToken: cancelToken, + #options: options, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i8.Future<_i6.Response> fetch(_i2.RequestOptions? requestOptions) => + (super.noSuchMethod( + Invocation.method(#fetch, [requestOptions]), + returnValue: _i8.Future<_i6.Response>.value( + _FakeResponse_4( + this, + Invocation.method(#fetch, [requestOptions]), + ), + ), + ) + as _i8.Future<_i6.Response>); + + @override + _i7.Dio clone({ + _i2.BaseOptions? options, + _i3.Interceptors? interceptors, + _i4.HttpClientAdapter? httpClientAdapter, + _i5.Transformer? transformer, + }) => + (super.noSuchMethod( + Invocation.method(#clone, [], { + #options: options, + #interceptors: interceptors, + #httpClientAdapter: httpClientAdapter, + #transformer: transformer, + }), + returnValue: _FakeDio_5( + this, + Invocation.method(#clone, [], { + #options: options, + #interceptors: interceptors, + #httpClientAdapter: httpClientAdapter, + #transformer: transformer, + }), + ), + ) + as _i7.Dio); +} -- 2.51.2