diff --git a/lib/iden3comm/data/data_sources/remote_iden3comm_data_source.dart b/lib/iden3comm/data/data_sources/remote_iden3comm_data_source.dart index 89c327f5..89c89c2a 100644 --- a/lib/iden3comm/data/data_sources/remote_iden3comm_data_source.dart +++ b/lib/iden3comm/data/data_sources/remote_iden3comm_data_source.dart @@ -16,12 +16,10 @@ import 'package:polygonid_flutter_sdk/iden3comm/domain/exceptions/iden3comm_exce class RemoteIden3commDataSource { final Dio dio; - final http.Client client; final StacktraceManager _stacktraceManager; RemoteIden3commDataSource( this.dio, - this.client, this._stacktraceManager, ); @@ -98,92 +96,40 @@ class RemoteIden3commDataSource { throw NetworkException(errorMessage: "Invalid url", statusCode: 0); } - http.Response response = await client.post( - uri, - body: authToken, - headers: { - HttpHeaders.acceptHeader: '*/*', - HttpHeaders.contentTypeHeader: 'text/plain', - }, - ); - - if (response.statusCode != 200) { - logger().d( - 'refreshCredential Error: code: ${response.statusCode} msg: ${response.body}'); - _stacktraceManager.addError( - 'refreshCredential Error: $url response with\ncode: ${response.statusCode}\nmsg: ${response.body}'); - throw NetworkException( - errorMessage: response.body, statusCode: response.statusCode); - } else { - FetchClaimResponseDTO fetchResponse = - FetchClaimResponseDTO.fromJson(json.decode(response.body)); + try { + final response = await dio.post( + url, + data: authToken, + options: Options( + headers: { + HttpHeaders.acceptHeader: '*/*', + HttpHeaders.contentTypeHeader: 'text/plain', + }, + receiveTimeout: const Duration(seconds: 30), + ), + ); - if (fetchResponse.type == FetchClaimResponseType.issuance) { - return ClaimDTO( - id: fetchResponse.credential.id, - issuer: fetchResponse.from, - did: profileDid, - type: fetchResponse.credential.credentialSubject.type, - expiration: fetchResponse.credential.expirationDate, - info: fetchResponse.credential, - credentialRawValue: response.body, - ); - } else { - _stacktraceManager.addTrace( - "[RemoteIden3commDataSource] fetchClaim: UnsupportedFetchClaimTypeException"); + if (response.statusCode != 200) { + logger().d( + 'refreshCredential Error: code: ${response.statusCode} msg: ${response.data}'); _stacktraceManager.addError( - "[RemoteIden3commDataSource] fetchClaim: UnsupportedFetchClaimTypeException"); - throw UnsupportedFetchClaimTypeException( - type: fetchResponse.type.name, - errorMessage: - 'Unsupported fetch claim type: ${fetchResponse.type.name}\nShould be ${FetchClaimResponseType.issuance.name}', - ); - } - } - } - - Future fetchClaim({ - required String authToken, - required String url, - required String did, - }) { - _stacktraceManager.addTrace( - "[RemoteIden3commDataSource] fetchClaim: did:$did\nurl: $url\nauthToken: $authToken"); - logger().i( - "[RemoteIden3commDataSource] fetchClaim: did:$did\nurl: $url\nauthToken: $authToken"); - return Future.value(Uri.parse(url)) - .then((uri) => client.post( - uri, - body: authToken, - headers: { - HttpHeaders.acceptHeader: '*/*', - HttpHeaders.contentTypeHeader: 'text/plain', - }, - )) - .then((response) { - logger() - .d("fetchClaim: code: ${response.statusCode} msg: ${response.body}"); - _stacktraceManager.addTrace( - "[RemoteIden3commDataSource] fetchClaim: ${response.statusCode} ${response.body}"); - if (response.statusCode == 200) { - Map jsonResponse = json.decode(response.body); - final fetchResponse = FetchClaimResponseDTO.fromJson(jsonResponse); + 'refreshCredential Error: $url response with\ncode: ${response.statusCode}\nmsg: ${response.data}'); + throw NetworkException( + errorMessage: response.data, statusCode: response.statusCode ?? 0); + } else { + FetchClaimResponseDTO fetchResponse = + FetchClaimResponseDTO.fromJson(response.data); if (fetchResponse.type == FetchClaimResponseType.issuance) { - logger().i( - "[RemoteIden3commDataSource] fetchClaim: ${fetchResponse.credential.toJson()}"); - final claimDTO = ClaimDTO( + return ClaimDTO( id: fetchResponse.credential.id, issuer: fetchResponse.from, - did: did, + did: profileDid, type: fetchResponse.credential.credentialSubject.type, expiration: fetchResponse.credential.expirationDate, info: fetchResponse.credential, - credentialRawValue: response.body, + credentialRawValue: json.encode(response.data), ); - logger().i( - "[RemoteIden3commDataSource] fetchClaim: ${claimDTO.info.toJson()}"); - return claimDTO; } else { _stacktraceManager.addTrace( "[RemoteIden3commDataSource] fetchClaim: UnsupportedFetchClaimTypeException"); @@ -195,17 +141,113 @@ class RemoteIden3commDataSource { 'Unsupported fetch claim type: ${fetchResponse.type.name}\nShould be ${FetchClaimResponseType.issuance.name}', ); } + } + } on DioException catch (e) { + if (e.type == DioExceptionType.connectionTimeout || + e.type == DioExceptionType.receiveTimeout) { + _stacktraceManager.addError( + 'refreshCredential error: $url response with\ncode: ${e.response?.statusCode}\nmsg: ${e.response?.data}'); + throw NetworkException( + errorMessage: "Connection timeout while refreshing credential.", + statusCode: e.response?.statusCode ?? 0, + ); } else { + _stacktraceManager.addError( + 'refreshCredential error: $url response with\ncode: ${e.response?.statusCode}\nmsg: ${e.response?.data}'); + rethrow; + } + } catch (e) { + logger().e('refreshCredential error: $e'); + rethrow; + } + } + + Future fetchClaim({ + required String authToken, + required String url, + required String did, + }) { + _stacktraceManager.addTrace( + "[RemoteIden3commDataSource] fetchClaim: did:$did\nurl: $url\nauthToken: $authToken"); + logger().i( + "[RemoteIden3commDataSource] fetchClaim: did:$did\nurl: $url\nauthToken: $authToken"); + + try { + return Future.value(Uri.parse(url)) + .then((uri) => dio.post( + url, + data: authToken, + options: Options( + headers: { + HttpHeaders.acceptHeader: '*/*', + HttpHeaders.contentTypeHeader: 'text/plain', + }, + receiveTimeout: const Duration(seconds: 30), + ), + )) + .then((response) { logger().d( - 'fetchClaim Error: code: ${response.statusCode} msg: ${response.body}'); + "fetchClaim: code: ${response.statusCode} msg: ${response.data}"); + _stacktraceManager.addTrace( + "[RemoteIden3commDataSource] fetchClaim: ${response.statusCode} ${response.data}"); + if (response.statusCode == 200) { + final fetchResponse = FetchClaimResponseDTO.fromJson(response.data); + + if (fetchResponse.type == FetchClaimResponseType.issuance) { + logger().i( + "[RemoteIden3commDataSource] fetchClaim: ${fetchResponse.credential.toJson()}"); + final claimDTO = ClaimDTO( + id: fetchResponse.credential.id, + issuer: fetchResponse.from, + did: did, + type: fetchResponse.credential.credentialSubject.type, + expiration: fetchResponse.credential.expirationDate, + info: fetchResponse.credential, + credentialRawValue: jsonEncode(response.data), + ); + logger().i( + "[RemoteIden3commDataSource] fetchClaim: ${claimDTO.info.toJson()}"); + return claimDTO; + } else { + _stacktraceManager.addTrace( + "[RemoteIden3commDataSource] fetchClaim: UnsupportedFetchClaimTypeException"); + _stacktraceManager.addError( + "[RemoteIden3commDataSource] fetchClaim: UnsupportedFetchClaimTypeException"); + throw UnsupportedFetchClaimTypeException( + type: fetchResponse.type.name, + errorMessage: + 'Unsupported fetch claim type: ${fetchResponse.type.name}\nShould be ${FetchClaimResponseType.issuance.name}', + ); + } + } else { + logger().d( + 'fetchClaim Error: code: ${response.statusCode} msg: ${response.data}'); + _stacktraceManager.addError( + 'fetchClaim Error: $url response with\ncode: ${response.statusCode}\nmsg: ${response.data}'); + throw NetworkException( + errorMessage: response.data, + statusCode: response.statusCode ?? 0, + ); + } + }); + } on DioException catch (e) { + if (e.type == DioExceptionType.connectionTimeout || + e.type == DioExceptionType.receiveTimeout) { _stacktraceManager.addError( - 'fetchClaim Error: $url response with\ncode: ${response.statusCode}\nmsg: ${response.body}'); + 'fetchClaim error: $url response with\ncode: ${e.response?.statusCode}\nmsg: ${e.response?.data}'); throw NetworkException( - errorMessage: response.body, - statusCode: response.statusCode, + errorMessage: "Connection timeout while fetching claim.", + statusCode: e.response?.statusCode ?? 0, ); + } else { + _stacktraceManager.addError( + 'fetchClaim error: $url response with\ncode: ${e.response?.statusCode}\nmsg: ${e.response?.data}'); + rethrow; } - }); + } catch (e) { + logger().e('fetchClaim error: $e'); + rethrow; + } } Future> fetchSchema({required String url}) async { diff --git a/lib/sdk/di/injector.config.dart b/lib/sdk/di/injector.config.dart index 6bd656b6..0402a047 100644 --- a/lib/sdk/di/injector.config.dart +++ b/lib/sdk/di/injector.config.dart @@ -570,16 +570,15 @@ extension GetItInjectableX on _i174.GetIt { () => _i98.JWZMapper(gh<_i267.StacktraceManager>())); gh.factory<_i897.GetIden3MessageUseCase>( () => _i897.GetIden3MessageUseCase(gh<_i267.StacktraceManager>())); + gh.factory<_i310.StoreRef>>( + () => databaseModule.interactionStore, + instanceName: 'interactionStore', + ); gh.factory<_i409.RemoteIden3commDataSource>( () => _i409.RemoteIden3commDataSource( gh<_i361.Dio>(), - gh<_i519.Client>(), gh<_i267.StacktraceManager>(), )); - gh.factory<_i310.StoreRef>>( - () => databaseModule.interactionStore, - instanceName: 'interactionStore', - ); gh.factory<_i425.SecureInteractionStoreRefWrapper>(() => _i425.SecureInteractionStoreRefWrapper( gh<_i310.StoreRef>>( @@ -684,7 +683,7 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i232.SecureStorageProfilesDataSource>(() => _i232.SecureStorageProfilesDataSource( gh<_i232.SecureStorageProfilesStoreRefWrapper>())); - gh.factory<_i969.LocalClaimDataSource>(() => _i969.LocalClaimDataSource( + gh.singleton<_i969.LocalClaimDataSource>(() => _i969.LocalClaimDataSource( gh<_i758.LibPolygonIdCoreCredentialDataSource>())); gh.factory<_i272.CacheCredentialDataSource>(() => _i272.CacheCredentialDataSource( diff --git a/test/iden3comm/data/data_sources/remote_iden3comm_data_source_test.dart b/test/iden3comm/data/data_sources/remote_iden3comm_data_source_test.dart index 55321cc2..47670d0a 100644 --- a/test/iden3comm/data/data_sources/remote_iden3comm_data_source_test.dart +++ b/test/iden3comm/data/data_sources/remote_iden3comm_data_source_test.dart @@ -23,38 +23,38 @@ const token = "theToken"; const url = "theUrl"; const identifier = "theIdentifier"; Response dioResponse = Response( - data: mockFetchClaim, + data: jsonDecode(mockFetchClaim), statusCode: 200, requestOptions: RequestOptions(path: url)); Response dioErrorResponse = Response( - data: mockFetchClaim, + data: 'Error message', statusCode: 444, requestOptions: RequestOptions(path: url)); -http.Response response = http.Response(mockFetchClaim, 200); -http.Response errorResponse = http.Response(mockFetchClaim, 444); -http.Response otherTypeResponse = http.Response(mockOtherTypeFetchClaim, 200); +Response otherTypeResponse = Response( + data: jsonDecode(mockOtherTypeFetchClaim), + statusCode: 200, + requestOptions: RequestOptions(path: url)); final exception = Exception(); /// We assume [FetchClaimResponseDTO] has been tested final fetchClaimDTO = FetchClaimResponseDTO.fromJson(jsonDecode(mockFetchClaim)); final claim = ClaimDTO( - id: fetchClaimDTO.credential.id, - issuer: fetchClaimDTO.from, - did: identifier, - expiration: fetchClaimDTO.credential.expirationDate, - type: fetchClaimDTO.credential.credentialSubject.type, - info: fetchClaimDTO.credential, - credentialRawValue: mockFetchClaim); + id: fetchClaimDTO.credential.id, + issuer: fetchClaimDTO.from, + did: identifier, + expiration: fetchClaimDTO.credential.expirationDate, + type: fetchClaimDTO.credential.credentialSubject.type, + info: fetchClaimDTO.credential, + credentialRawValue: jsonEncode(jsonDecode(mockFetchClaim)), +); //DEPENDENCIES MockDio dio = MockDio(); -MockClient client = MockClient(); MockStacktraceManager stacktraceStreamManager = MockStacktraceManager(); RemoteIden3commDataSource dataSource = RemoteIden3commDataSource( dio, - client, stacktraceStreamManager, ); @@ -106,47 +106,48 @@ void main() { }); test( - "Given token and authRequest, when I call authWithToken and a server error occurred with status code 450, then I expect an UnknownApiException to be thrown", - () async { - // - when( - dio.post( - any, - data: anyNamed('data'), - options: anyNamed('options'), - ), - ).thenAnswer( - (realInvocation) => Future.value(dioErrorResponse), - ); + "Given token and authRequest, when I call authWithToken and a server error occurred with status code 450, then I expect a DioException to be thrown", + () async { + // Arrange + when( + dio.post( + any, + data: anyNamed('data'), + options: anyNamed('options'), + ), + ).thenAnswer( + (_) => Future.value(dioErrorResponse), + ); - // - await expectLater( - dataSource.authWithToken( - url: CommonMocks.url, - token: CommonMocks.token, - ), - throwsA(isA()), - ); + // Act & Assert + await expectLater( + dataSource.authWithToken( + url: CommonMocks.url, + token: CommonMocks.token, + ), + throwsA(isA()), + ); - // - var captured = verify( - dio.post( - captureAny, - data: captureAnyNamed('data'), - options: captureAnyNamed('options'), - ), - ).captured; + // Verify + var captured = verify( + dio.post( + captureAny, + data: captureAnyNamed('data'), + options: captureAnyNamed('options'), + ), + ).captured; - expect(captured[0], CommonMocks.url); - expect(captured[1], CommonMocks.token); - expect( - captured[2].headers, - { - HttpHeaders.acceptHeader: '*/*', - HttpHeaders.contentTypeHeader: 'text/plain', - }, - ); - }); + expect(captured[0], CommonMocks.url); + expect(captured[1], CommonMocks.token); + expect( + captured[2].headers, + { + HttpHeaders.acceptHeader: '*/*', + HttpHeaders.contentTypeHeader: 'text/plain', + }, + ); + }, + ); }); group("Fetch credential", () { @@ -154,9 +155,8 @@ void main() { "Given parameters, when I call fetchClaim, then I expect a Claim to be returned", () async { // Given - when(client.post(any, - body: anyNamed('body'), headers: anyNamed('headers'))) - .thenAnswer((realInvocation) => Future.value(response)); + when(dio.post(any, data: anyNamed('data'), options: anyNamed('options'))) + .thenAnswer((realInvocation) => Future.value(dioResponse)); // When expect( @@ -165,14 +165,14 @@ void main() { claim); // Then - var captured = verify(client.post(captureAny, - body: captureAnyNamed('body'), - headers: captureAnyNamed('headers'))) + var captured = verify(dio.post(captureAny, + data: captureAnyNamed('data'), + options: captureAnyNamed('options'))) .captured; - expect(captured[0], Uri.parse(url)); + expect(captured[0], url); expect(captured[1], token); - expect(captured[2], { + expect(captured[2].headers, { HttpHeaders.acceptHeader: '*/*', HttpHeaders.contentTypeHeader: 'text/plain', }); @@ -182,9 +182,8 @@ void main() { "Given parameters, when I call fetchClaim and a server error occurred, then I expect an NetworkException to be thrown", () async { // Given - when(client.post(any, - body: anyNamed('body'), headers: anyNamed('headers'))) - .thenAnswer((realInvocation) => Future.value(errorResponse)); + when(dio.post(any, data: anyNamed('data'), options: anyNamed('options'))) + .thenAnswer((realInvocation) => Future.value(dioErrorResponse)); // When await expectLater( @@ -192,14 +191,14 @@ void main() { throwsA(isA())); // Then - var captured = verify(client.post(captureAny, - body: captureAnyNamed('body'), - headers: captureAnyNamed('headers'))) + var captured = verify(dio.post(captureAny, + data: captureAnyNamed('data'), + options: captureAnyNamed('options'))) .captured; - expect(captured[0], Uri.parse(url)); + expect(captured[0], url); expect(captured[1], token); - expect(captured[2], { + expect(captured[2].headers, { HttpHeaders.acceptHeader: '*/*', HttpHeaders.contentTypeHeader: 'text/plain', }); @@ -209,8 +208,7 @@ void main() { "Given parameters, when I call fetchClaim and an unsupported FetchClaimResponseType is returned, then I expect an UnsupportedFetchClaimTypeException to be thrown", () async { // Given - when(client.post(any, - body: anyNamed('body'), headers: anyNamed('headers'))) + when(dio.post(any, data: anyNamed('data'), options: anyNamed('options'))) .thenAnswer((realInvocation) => Future.value(otherTypeResponse)); // When @@ -223,14 +221,14 @@ void main() { }); // Then - var captured = verify(client.post(captureAny, - body: captureAnyNamed('body'), - headers: captureAnyNamed('headers'))) + var captured = verify(dio.post(captureAny, + data: captureAnyNamed('data'), + options: captureAnyNamed('options'))) .captured; - expect(captured[0], Uri.parse(url)); + expect(captured[0], url); expect(captured[1], token); - expect(captured[2], { + expect(captured[2].headers, { HttpHeaders.acceptHeader: '*/*', HttpHeaders.contentTypeHeader: 'text/plain', }); @@ -240,8 +238,7 @@ void main() { "Given parameters, when I call fetchClaim and an error occurred, then I expect an exception to be thrown", () async { // Given - when(client.post(any, - body: anyNamed('body'), headers: anyNamed('headers'))) + when(dio.post(any, data: anyNamed('data'), options: anyNamed('options'))) .thenAnswer((realInvocation) => Future.error(exception)); // When @@ -250,14 +247,14 @@ void main() { throwsA(exception)); // Then - var captured = verify(client.post(captureAny, - body: captureAnyNamed('body'), - headers: captureAnyNamed('headers'))) + var captured = verify(dio.post(captureAny, + data: captureAnyNamed('data'), + options: captureAnyNamed('options'))) .captured; - expect(captured[0], Uri.parse(url)); + expect(captured[0], url); expect(captured[1], token); - expect(captured[2], { + expect(captured[2].headers, { HttpHeaders.acceptHeader: '*/*', HttpHeaders.contentTypeHeader: 'text/plain', });