diff --git a/packages/alice_chopper/CHANGELOG.md b/packages/alice_chopper/CHANGELOG.md index 2a9c7084..71f2080a 100644 --- a/packages/alice_chopper/CHANGELOG.md +++ b/packages/alice_chopper/CHANGELOG.md @@ -2,6 +2,8 @@ * Added lint for trailing commas. * General refactor of code base. +* Added support for form data. +* Added tests. # 1.0.5 diff --git a/packages/alice_chopper/pubspec.yaml b/packages/alice_chopper/pubspec.yaml index af9396f0..83bc07f5 100644 --- a/packages/alice_chopper/pubspec.yaml +++ b/packages/alice_chopper/pubspec.yaml @@ -27,3 +27,7 @@ dev_dependencies: test: ^1.25.2 alice_test: ^1.0.0 mocktail: ^1.0.4 + build_runner: ^2.4.9 + chopper_generator: ^8.0.0 + json_serializable: ^6.8.0 + json_annotation: ^4.9.0 diff --git a/packages/alice_chopper/test/alice_chopper_adapter_test.dart b/packages/alice_chopper/test/alice_chopper_adapter_test.dart index 857b58b5..45ab8af5 100644 --- a/packages/alice_chopper/test/alice_chopper_adapter_test.dart +++ b/packages/alice_chopper/test/alice_chopper_adapter_test.dart @@ -15,6 +15,10 @@ import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; import 'package:http/http.dart' as http; +import 'invalid_model.dart'; +import 'invalid_service.dart'; +import 'json_serializable_converter.dart'; + void main() { final baseUrl = Uri.parse('https://test.com'); late AliceCore aliceCore; @@ -366,5 +370,81 @@ void main() { verify(() => aliceCore.addError(any(that: errorMatcher), any())); }); + + test("should handle call with error when model fails", () async { + final mockClient = MockClient((request) async { + return http.Response( + '{"id": 0}', + 200, + headers: {'content-type': 'application/json'}, + ); + }); + + chopperClient = ChopperClient( + baseUrl: baseUrl, + client: mockClient, + interceptors: [aliceChopperAdapter], + services: [InvalidService.create()], + converter: const JsonSerializableConverter( + {InvalidModel: InvalidModel.fromJson}, + ), + ); + + final service = chopperClient.getService(); + try { + await service.get(0); + } catch (_) {} + + final requestMatcher = buildRequestMatcher( + checkTime: true, + ); + + final responseMatcher = buildResponseMatcher(checkTime: true); + + final callMatcher = buildCallMatcher( + checkId: true, + checkTime: true, + secured: true, + loading: true, + client: 'Chopper', + method: 'GET', + endpoint: '/posts/0', + server: 'test.com', + uri: 'https://test.com/posts/0', + duration: 0, + request: requestMatcher, + response: responseMatcher, + ); + + verify(() => aliceCore.addCall(any(that: callMatcher))); + + final nextResponseMatcher = buildResponseMatcher( + status: -1, + size: 0, + checkTime: true, + ); + + verify( + () => aliceCore.addResponse( + any(that: nextResponseMatcher), + any(), + ), + ); + + final errorMatcher = buildErrorMatcher( + checkError: true, + checkStacktrace: true, + ); + + verify(() => aliceCore.addError(any(that: errorMatcher), any())); + + final logMatcher = buildLogMatcher( + checkMessage: true, + checkError: true, + checkStacktrace: true, + checkTime: true, + ); + verify(() => aliceCore.addLog(any(that: logMatcher))); + }); }); } diff --git a/packages/alice_chopper/test/invalid_model.dart b/packages/alice_chopper/test/invalid_model.dart new file mode 100644 index 00000000..bd2cc36e --- /dev/null +++ b/packages/alice_chopper/test/invalid_model.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'invalid_model.g.dart'; + +@JsonSerializable() +class InvalidModel with EquatableMixin { + const InvalidModel({ + this.id, + }); + + // should be `int?` but we want to test the error + final String? id; + + InvalidModel copyWith({ + String? id, + }) => + InvalidModel( + id: id ?? this.id, + ); + + factory InvalidModel.fromJson(Map json) => + _$InvalidModelFromJson(json); + + Map toJson() => _$InvalidModelToJson(this); + + @override + List get props => [ + id, + ]; +} diff --git a/packages/alice_chopper/test/invalid_model.g.dart b/packages/alice_chopper/test/invalid_model.g.dart new file mode 100644 index 00000000..80555b8c --- /dev/null +++ b/packages/alice_chopper/test/invalid_model.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'invalid_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +InvalidModel _$InvalidModelFromJson(Map json) => InvalidModel( + id: json['id'] as String?, + ); + +Map _$InvalidModelToJson(InvalidModel instance) => + { + 'id': instance.id, + }; diff --git a/packages/alice_chopper/test/invalid_service.chopper.dart b/packages/alice_chopper/test/invalid_service.chopper.dart new file mode 100644 index 00000000..1502b9e5 --- /dev/null +++ b/packages/alice_chopper/test/invalid_service.chopper.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'invalid_service.dart'; + +// ************************************************************************** +// ChopperGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +final class _$InvalidService extends InvalidService { + _$InvalidService([ChopperClient? client]) { + if (client == null) return; + this.client = client; + } + + @override + final Type definitionType = InvalidService; + + @override + Future> get(int id) { + final Uri $url = Uri.parse('/posts/${id}'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client + .send($request) + .timeout(const Duration(microseconds: 10000000)); + } +} diff --git a/packages/alice_chopper/test/invalid_service.dart b/packages/alice_chopper/test/invalid_service.dart new file mode 100644 index 00000000..a5193fe2 --- /dev/null +++ b/packages/alice_chopper/test/invalid_service.dart @@ -0,0 +1,14 @@ +import 'package:chopper/chopper.dart'; + +import 'invalid_model.dart'; + +part 'invalid_service.chopper.dart'; + +@ChopperApi(baseUrl: '/posts') +abstract class InvalidService extends ChopperService { + static InvalidService create([ChopperClient? client]) => + _$InvalidService(client); + + @Get(path: '/{id}', timeout: Duration(seconds: 10)) + Future> get(@Path() int id); +} diff --git a/packages/alice_chopper/test/json_serializable_converter.dart b/packages/alice_chopper/test/json_serializable_converter.dart new file mode 100644 index 00000000..39a34171 --- /dev/null +++ b/packages/alice_chopper/test/json_serializable_converter.dart @@ -0,0 +1,49 @@ +import 'dart:async' show FutureOr; + +import 'package:chopper/chopper.dart'; + +typedef JsonFactory = T Function(Map json); + +class JsonSerializableConverter extends JsonConverter { + final Map factories; + + const JsonSerializableConverter(this.factories); + + T? _decodeMap(Map values) { + /// Get jsonFactory using Type parameters + /// if not found or invalid, throw error or return null + final jsonFactory = factories[T]; + if (jsonFactory == null || jsonFactory is! JsonFactory) { + /// throw serializer not found error; + return null; + } + + return jsonFactory(values); + } + + List _decodeList(Iterable values) => + values.where((v) => v != null).map((v) => _decode(v)).toList(); + + dynamic _decode(entity) { + if (entity is Iterable) return _decodeList(entity as List); + + if (entity is Map) return _decodeMap(entity as Map); + + return entity; + } + + @override + FutureOr> convertResponse( + Response response, + ) async { + // use [JsonConverter] to decode json + final jsonRes = await super.convertResponse(response); + + return jsonRes.copyWith(body: _decode(jsonRes.body)); + } + + @override + // all objects should implements toJson method + // ignore: unnecessary_overrides + Request convertRequest(Request request) => super.convertRequest(request); +}