From 9df4ed15a2f61da1cac2b656f77e07e653cb70f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klemen=20Tu=C5=A1ar?= Date: Sat, 10 Dec 2022 13:02:38 +0000 Subject: [PATCH 1/6] :white_check_mark: Add authenticator tests --- chopper/test/authenticator_test.dart | 221 +++++++++++++++++++++++++++ chopper/test/fake_authenticator.dart | 23 +++ 2 files changed, 244 insertions(+) create mode 100644 chopper/test/authenticator_test.dart create mode 100644 chopper/test/fake_authenticator.dart diff --git a/chopper/test/authenticator_test.dart b/chopper/test/authenticator_test.dart new file mode 100644 index 00000000..e89ff36c --- /dev/null +++ b/chopper/test/authenticator_test.dart @@ -0,0 +1,221 @@ +import 'dart:convert'; + +import 'package:chopper/chopper.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +import 'fake_authenticator.dart'; +import 'test_service.dart'; + +void main() async { + final Uri baseUrl = Uri.parse('http://localhost:8000'); + + group('Without Authenticator', () { + ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( + baseUrl: baseUrl, + client: httpClient, + interceptors: [ + (Request req) => applyHeader(req, 'foo', 'bar'), + ], + converter: JsonConverter(), + ); + + test('GET', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/get?key=val'), + ); + expect(request.method, equals('GET')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.get( + Uri( + path: '/test/get', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('POST', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/post?key=val'), + ); + expect(request.method, equals('POST')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.post( + Uri( + path: '/test/post', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + }); + + group('With Authenticator', () { + ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( + baseUrl: baseUrl, + services: [ + // the generated service + HttpTestService.create(), + ], + interceptors: [ + (Request req) => applyHeader(req, 'foo', 'bar'), + ], + authenticator: FakeAuthenticator(), + client: httpClient, + converter: JsonConverter(), + ); + + late bool authenticated; + late Map tested; + + setUp(() { + authenticated = false; + tested = { + 'unauthenticated': false, + 'authenticated': false, + }; + }); + + tearDown(() { + authenticated = false; + tested = { + 'unauthenticated': false, + 'authenticated': false, + }; + }); + + test('GET', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/get?key=val'), + ); + expect(request.method, equals('GET')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + + if (!authenticated) { + tested['unauthenticated'] = true; + authenticated = true; + + return http.Response('unauthorized', 401); + } else { + tested['authenticated'] = true; + expect(request.headers['authorization'], equals('some_fake_token')); + } + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.get( + Uri( + path: '/test/get', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + expect(tested['authenticated'], equals(true)); + expect(tested['unauthenticated'], equals(true)); + + httpClient.close(); + }); + + test('POST', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/post?key=val'), + ); + expect(request.method, equals('POST')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + if (!authenticated) { + tested['unauthenticated'] = true; + authenticated = true; + + return http.Response('unauthorized', 401); + } else { + tested['authenticated'] = true; + expect(request.headers['authorization'], equals('some_fake_token')); + } + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.post( + Uri( + path: '/test/post', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + expect(tested['authenticated'], equals(true)); + expect(tested['unauthenticated'], equals(true)); + + httpClient.close(); + }); + }); +} diff --git a/chopper/test/fake_authenticator.dart b/chopper/test/fake_authenticator.dart new file mode 100644 index 00000000..22de9356 --- /dev/null +++ b/chopper/test/fake_authenticator.dart @@ -0,0 +1,23 @@ +import 'dart:async' show FutureOr; + +import 'package:chopper/chopper.dart'; + +class FakeAuthenticator extends Authenticator { + @override + FutureOr authenticate( + Request request, + Response response, [ + Request? originalRequest, + ]) async { + if (response.statusCode == 401) { + return request.copyWith( + headers: { + ...request.headers, + 'authorization': 'some_fake_token', + }, + ); + } + + return null; + } +} From 5a36d27238c61873f6e931d77e6bf0eab1cc50ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klemen=20Tu=C5=A1ar?= Date: Sat, 10 Dec 2022 13:30:04 +0000 Subject: [PATCH 2/6] :white_check_mark: Add authenticator tests --- chopper/test/authenticator_test.dart | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/chopper/test/authenticator_test.dart b/chopper/test/authenticator_test.dart index e89ff36c..d6cd3f45 100644 --- a/chopper/test/authenticator_test.dart +++ b/chopper/test/authenticator_test.dart @@ -6,7 +6,6 @@ import 'package:http/testing.dart'; import 'package:test/test.dart'; import 'fake_authenticator.dart'; -import 'test_service.dart'; void main() async { final Uri baseUrl = Uri.parse('http://localhost:8000'); @@ -94,16 +93,12 @@ void main() async { group('With Authenticator', () { ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( baseUrl: baseUrl, - services: [ - // the generated service - HttpTestService.create(), - ], + client: httpClient, interceptors: [ (Request req) => applyHeader(req, 'foo', 'bar'), ], - authenticator: FakeAuthenticator(), - client: httpClient, converter: JsonConverter(), + authenticator: FakeAuthenticator(), ); late bool authenticated; From bc0e50ccc2a6f867793e31f4a059273c2b0c9e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klemen=20Tu=C5=A1ar?= Date: Sat, 10 Dec 2022 13:37:30 +0000 Subject: [PATCH 3/6] :white_check_mark: Add authenticator tests --- chopper/test/authenticator_test.dart | 73 +++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/chopper/test/authenticator_test.dart b/chopper/test/authenticator_test.dart index d6cd3f45..e27d484e 100644 --- a/chopper/test/authenticator_test.dart +++ b/chopper/test/authenticator_test.dart @@ -120,7 +120,35 @@ void main() async { }; }); - test('GET', () async { + test('GET authorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/get?key=val'), + ); + expect(request.method, equals('GET')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.get( + Uri( + path: '/test/get', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('GET unauthorized', () async { final httpClient = MockClient((request) async { expect( request.url.toString(), @@ -160,7 +188,48 @@ void main() async { httpClient.close(); }); - test('POST', () async { + test('POST authorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/post?key=val'), + ); + expect(request.method, equals('POST')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.post( + Uri( + path: '/test/post', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('POST unauthorized', () async { final httpClient = MockClient((request) async { expect( request.url.toString(), From 4169b1bc186b23720a0168718ab5e02573a0a818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klemen=20Tu=C5=A1ar?= Date: Sat, 10 Dec 2022 13:46:53 +0000 Subject: [PATCH 4/6] :white_check_mark: Add authenticator tests --- chopper/test/authenticator_test.dart | 133 +++++---------------------- 1 file changed, 24 insertions(+), 109 deletions(-) diff --git a/chopper/test/authenticator_test.dart b/chopper/test/authenticator_test.dart index e27d484e..f2705460 100644 --- a/chopper/test/authenticator_test.dart +++ b/chopper/test/authenticator_test.dart @@ -10,117 +10,30 @@ import 'fake_authenticator.dart'; void main() async { final Uri baseUrl = Uri.parse('http://localhost:8000'); - group('Without Authenticator', () { - ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( - baseUrl: baseUrl, - client: httpClient, - interceptors: [ - (Request req) => applyHeader(req, 'foo', 'bar'), - ], - converter: JsonConverter(), - ); - - test('GET', () async { - final httpClient = MockClient((request) async { - expect( - request.url.toString(), - equals('$baseUrl/test/get?key=val'), - ); - expect(request.method, equals('GET')); - expect(request.headers['foo'], equals('bar')); - expect(request.headers['int'], equals('42')); - - return http.Response('ok', 200); - }); - - final chopper = buildClient(httpClient); - final response = await chopper.get( - Uri( - path: '/test/get', - queryParameters: {'key': 'val'}, - ), - headers: {'int': '42'}, + ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( + baseUrl: baseUrl, + client: httpClient, + interceptors: [ + (Request req) => applyHeader(req, 'foo', 'bar'), + ], + converter: JsonConverter(), + authenticator: FakeAuthenticator(), ); - expect(response.body, equals('ok')); - expect(response.statusCode, equals(200)); + late bool authenticated; + final Map tested = { + 'unauthenticated': false, + 'authenticated': false, + }; - httpClient.close(); - }); - - test('POST', () async { - final httpClient = MockClient((request) async { - expect( - request.url.toString(), - equals('$baseUrl/test/post?key=val'), - ); - expect(request.method, equals('POST')); - expect(request.headers['foo'], equals('bar')); - expect(request.headers['int'], equals('42')); - expect( - request.body, - jsonEncode( - { - 'name': 'john', - 'surname': 'doe', - }, - ), - ); - - return http.Response('ok', 200); - }); - - final chopper = buildClient(httpClient); - final response = await chopper.post( - Uri( - path: '/test/post', - queryParameters: {'key': 'val'}, - ), - headers: {'int': '42'}, - body: { - 'name': 'john', - 'surname': 'doe', - }, - ); - - expect(response.body, equals('ok')); - expect(response.statusCode, equals(200)); - - httpClient.close(); - }); + setUp(() { + authenticated = false; + tested['unauthenticated'] = false; + tested['authenticated'] = false; }); - group('With Authenticator', () { - ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( - baseUrl: baseUrl, - client: httpClient, - interceptors: [ - (Request req) => applyHeader(req, 'foo', 'bar'), - ], - converter: JsonConverter(), - authenticator: FakeAuthenticator(), - ); - - late bool authenticated; - late Map tested; - - setUp(() { - authenticated = false; - tested = { - 'unauthenticated': false, - 'authenticated': false, - }; - }); - - tearDown(() { - authenticated = false; - tested = { - 'unauthenticated': false, - 'authenticated': false, - }; - }); - - test('GET authorized', () async { + group('GET', () { + test('authorized', () async { final httpClient = MockClient((request) async { expect( request.url.toString(), @@ -148,7 +61,7 @@ void main() async { httpClient.close(); }); - test('GET unauthorized', () async { + test('unauthorized', () async { final httpClient = MockClient((request) async { expect( request.url.toString(), @@ -187,8 +100,10 @@ void main() async { httpClient.close(); }); + }); - test('POST authorized', () async { + group('POST', () { + test('authorized', () async { final httpClient = MockClient((request) async { expect( request.url.toString(), @@ -229,7 +144,7 @@ void main() async { httpClient.close(); }); - test('POST unauthorized', () async { + test('unauthorized', () async { final httpClient = MockClient((request) async { expect( request.url.toString(), From 80cc8088ea38c6f2fa92e3f095b989e86cc7aa7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klemen=20Tu=C5=A1ar?= Date: Sat, 10 Dec 2022 14:03:00 +0000 Subject: [PATCH 5/6] :white_check_mark: Add authenticator tests --- chopper/test/authenticator_test.dart | 194 ++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 1 deletion(-) diff --git a/chopper/test/authenticator_test.dart b/chopper/test/authenticator_test.dart index f2705460..8f4ebb20 100644 --- a/chopper/test/authenticator_test.dart +++ b/chopper/test/authenticator_test.dart @@ -1,4 +1,4 @@ -import 'dart:convert'; +import 'dart:convert' show jsonEncode; import 'package:chopper/chopper.dart'; import 'package:http/http.dart' as http; @@ -197,4 +197,196 @@ void main() async { httpClient.close(); }); }); + + group('PUT', () { + test('authorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/put?key=val'), + ); + expect(request.method, equals('PUT')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.put( + Uri( + path: '/test/put', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('unauthorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/put?key=val'), + ); + expect(request.method, equals('PUT')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + if (!authenticated) { + tested['unauthenticated'] = true; + authenticated = true; + + return http.Response('unauthorized', 401); + } else { + tested['authenticated'] = true; + expect(request.headers['authorization'], equals('some_fake_token')); + } + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.put( + Uri( + path: '/test/put', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + expect(tested['authenticated'], equals(true)); + expect(tested['unauthenticated'], equals(true)); + + httpClient.close(); + }); + }); + + group('PATCH', () { + test('authorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/patch?key=val'), + ); + expect(request.method, equals('PATCH')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.patch( + Uri( + path: '/test/patch', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('unauthorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/patch?key=val'), + ); + expect(request.method, equals('PATCH')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + if (!authenticated) { + tested['unauthenticated'] = true; + authenticated = true; + + return http.Response('unauthorized', 401); + } else { + tested['authenticated'] = true; + expect(request.headers['authorization'], equals('some_fake_token')); + } + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.patch( + Uri( + path: '/test/patch', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + expect(tested['authenticated'], equals(true)); + expect(tested['unauthenticated'], equals(true)); + + httpClient.close(); + }); + }); } From e0f94a6df0981a4f8e0b346ed4b8b3f7e794f4de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klemen=20Tu=C5=A1ar?= Date: Sat, 10 Dec 2022 14:13:28 +0000 Subject: [PATCH 6/6] :bug: #388 Authenticator double encodes body --- chopper/lib/src/base.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index 624607b3..4fa556c2 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -293,9 +293,10 @@ class ChopperClient { ConvertRequest? requestConverter, ConvertResponse? responseConverter, }) async { - var req = await _interceptRequest( + final Request req = await _interceptRequest( await _handleRequestConverter(request, requestConverter), ); + _requestController.add(req); final streamRes = await httpClient.send(await req.toBaseRequest()); @@ -307,7 +308,11 @@ class ChopperClient { dynamic res = Response(response, response.body); if (authenticator != null) { - var updatedRequest = await authenticator!.authenticate(req, res, request); + final Request? updatedRequest = await authenticator!.authenticate( + request, + res, + request, + ); if (updatedRequest != null) { res = await send(