From e8b46a0a54796abd6dfb1f435780d26f15ea9018 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Mon, 2 Sep 2024 09:30:26 +0200 Subject: [PATCH 1/2] refactor(neon_http_client): split into separate interceptor client package Signed-off-by: Nikolas Rimikis --- packages/interceptor_http_client/LICENSE | 1 + .../analysis_options.yaml | 6 + .../interceptor_http_client/dart_test.yaml | 10 ++ .../lib/interceptor_http_client.dart | 5 + .../lib/src/interceptor_http_client.dart | 95 +++++++++++++ .../src/interceptors/cookie_interceptor.dart | 6 +- .../src/interceptors/http_interceptor.dart | 4 +- .../lib/src/interceptors/interceptors.dart | 2 + packages/interceptor_http_client/pubspec.yaml | 29 ++++ .../pubspec_overrides.yaml | 6 + .../test/client_conformance_test.dart | 44 ++++++ .../test/interceptor_http_client_test.dart | 133 ++++++++++++++++++ .../interceptors/cookie_interceptor_test.dart | 3 +- .../interceptors/http_interceptor_test.dart | 2 +- packages/neon_framework/example/pubspec.lock | 7 + .../example/pubspec_overrides.yaml | 4 +- .../account_repository/pubspec_overrides.yaml | 4 +- .../dashboard_app/pubspec_overrides.yaml | 4 +- .../packages/files_app/pubspec_overrides.yaml | 4 +- .../lib/neon_http_client.dart | 4 +- .../authorization_throttling_interceptor.dart | 2 +- .../interceptors/base_header_interceptor.dart | 2 +- .../src/interceptors/csrf_interceptor.dart | 4 +- .../lib/src/interceptors/interceptors.dart | 3 +- .../lib/src/neon_http_client.dart | 92 ++---------- .../packages/neon_http_client/pubspec.yaml | 5 +- .../neon_http_client/pubspec_overrides.yaml | 2 + .../test/client_conformance_test.dart | 37 +++-- .../test/neon_http_client_test.dart | 2 +- .../packages/news_app/pubspec_overrides.yaml | 4 +- .../packages/notes_app/pubspec_overrides.yaml | 4 +- .../notifications_app/pubspec_overrides.yaml | 4 +- .../packages/talk_app/pubspec_overrides.yaml | 4 +- .../neon_framework/pubspec_overrides.yaml | 4 +- .../nextcloud_test/pubspec_overrides.yaml | 4 +- packages/nextcloud/pubspec_overrides.yaml | 4 +- 36 files changed, 430 insertions(+), 120 deletions(-) create mode 120000 packages/interceptor_http_client/LICENSE create mode 100644 packages/interceptor_http_client/analysis_options.yaml create mode 100644 packages/interceptor_http_client/dart_test.yaml create mode 100644 packages/interceptor_http_client/lib/interceptor_http_client.dart create mode 100644 packages/interceptor_http_client/lib/src/interceptor_http_client.dart rename packages/{neon_framework/packages/neon_http_client => interceptor_http_client}/lib/src/interceptors/cookie_interceptor.dart (93%) rename packages/{neon_framework/packages/neon_http_client => interceptor_http_client}/lib/src/interceptors/http_interceptor.dart (90%) create mode 100644 packages/interceptor_http_client/lib/src/interceptors/interceptors.dart create mode 100644 packages/interceptor_http_client/pubspec.yaml create mode 100644 packages/interceptor_http_client/pubspec_overrides.yaml create mode 100644 packages/interceptor_http_client/test/client_conformance_test.dart create mode 100644 packages/interceptor_http_client/test/interceptor_http_client_test.dart rename packages/{neon_framework/packages/neon_http_client => interceptor_http_client}/test/interceptors/cookie_interceptor_test.dart (96%) rename packages/{neon_framework/packages/neon_http_client => interceptor_http_client}/test/interceptors/http_interceptor_test.dart (90%) diff --git a/packages/interceptor_http_client/LICENSE b/packages/interceptor_http_client/LICENSE new file mode 120000 index 00000000000..af8c58b151a --- /dev/null +++ b/packages/interceptor_http_client/LICENSE @@ -0,0 +1 @@ +../../assets/AGPL-3.0.txt \ No newline at end of file diff --git a/packages/interceptor_http_client/analysis_options.yaml b/packages/interceptor_http_client/analysis_options.yaml new file mode 100644 index 00000000000..f0a42286945 --- /dev/null +++ b/packages/interceptor_http_client/analysis_options.yaml @@ -0,0 +1,6 @@ +include: package:neon_lints/dart.yaml + +custom_lint: + rules: + - avoid_exports: false + - avoid_dart_io: false diff --git a/packages/interceptor_http_client/dart_test.yaml b/packages/interceptor_http_client/dart_test.yaml new file mode 100644 index 00000000000..20d3df48b98 --- /dev/null +++ b/packages/interceptor_http_client/dart_test.yaml @@ -0,0 +1,10 @@ +platforms: + - vm + - chrome + +define_platforms: + chromium: + name: Chromium + extends: chrome + settings: + executable: chromium diff --git a/packages/interceptor_http_client/lib/interceptor_http_client.dart b/packages/interceptor_http_client/lib/interceptor_http_client.dart new file mode 100644 index 00000000000..4769fa678bd --- /dev/null +++ b/packages/interceptor_http_client/lib/interceptor_http_client.dart @@ -0,0 +1,5 @@ +/// A http client that allows to register interceptors for requests and responses. +library; + +export 'src/interceptor_http_client.dart'; +export 'src/interceptors/interceptors.dart'; diff --git a/packages/interceptor_http_client/lib/src/interceptor_http_client.dart b/packages/interceptor_http_client/lib/src/interceptor_http_client.dart new file mode 100644 index 00000000000..622e3fbc556 --- /dev/null +++ b/packages/interceptor_http_client/lib/src/interceptor_http_client.dart @@ -0,0 +1,95 @@ +import 'dart:async'; + +import 'package:built_collection/built_collection.dart'; +import 'package:http/http.dart' as http; +import 'package:interceptor_http_client/src/interceptors/interceptors.dart'; +import 'package:meta/meta.dart'; + +/// An exception caused by an error in a [InterceptorHttpClient]. +abstract class InterceptorHttpClientException extends http.ClientException { + /// An exception caused by an error in a [InterceptorHttpClient]. + InterceptorHttpClientException(super.message, [super.uri]); +} + +/// An exception caused by a [HttpInterceptor]. +final class InterceptionException extends InterceptorHttpClientException { + /// Creates a new interceptor failure exception. + InterceptionException(super.message, [super.uri]); +} + +/// A http client for intercepting requests and responses. +class InterceptorHttpClient with http.BaseClient { + /// Creates a new interceptor http client. + const InterceptorHttpClient({ + required http.Client baseClient, + required this.interceptors, + }) : _baseClient = baseClient; + + /// The underlying HTTP client. + final http.Client _baseClient; + + /// The list of enabled interceptors. + @visibleForTesting + @protected + final BuiltList interceptors; + + @override + Future send(http.BaseRequest request) async { + var interceptedRequest = request; + for (final interceptor in interceptors) { + if (interceptor.shouldInterceptRequest(interceptedRequest)) { + try { + interceptedRequest = await interceptor.interceptRequest( + request: interceptedRequest, + ); + } catch (error, stackTrace) { + if (error is http.ClientException) { + rethrow; + } + + Error.throwWithStackTrace( + InterceptionException('Failed to intercept request', request.url), + stackTrace, + ); + } + } + } + + var interceptedResponse = await _baseClient.send(interceptedRequest); + + Uri url; + if (interceptedResponse case http.BaseResponseWithUrl(url: final responseUrl)) { + url = responseUrl; + } else { + url = interceptedRequest.url; + } + + for (final interceptor in interceptors) { + if (interceptor.shouldInterceptResponse(interceptedResponse)) { + try { + interceptedResponse = await interceptor.interceptResponse( + response: interceptedResponse, + url: url, + ); + } catch (error, stackTrace) { + if (error is http.ClientException) { + rethrow; + } + Error.throwWithStackTrace( + InterceptionException('Failed to intercept response', request.url), + stackTrace, + ); + } + } + } + + return interceptedResponse; + } + + @override + void close() { + _baseClient.close(); + + super.close(); + } +} diff --git a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/cookie_interceptor.dart b/packages/interceptor_http_client/lib/src/interceptors/cookie_interceptor.dart similarity index 93% rename from packages/neon_framework/packages/neon_http_client/lib/src/interceptors/cookie_interceptor.dart rename to packages/interceptor_http_client/lib/src/interceptors/cookie_interceptor.dart index a07f9124961..79997f905a1 100644 --- a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/cookie_interceptor.dart +++ b/packages/interceptor_http_client/lib/src/interceptors/cookie_interceptor.dart @@ -1,10 +1,10 @@ import 'dart:async'; +import 'dart:io' show Cookie, HttpHeaders; import 'package:cookie_store/cookie_store.dart'; import 'package:http/http.dart' as http; +import 'package:interceptor_http_client/src/interceptors/interceptors.dart'; import 'package:meta/meta.dart'; -import 'package:neon_http_client/src/interceptors/http_interceptor.dart'; -import 'package:universal_io/io.dart'; /// A HttpInterceptor to implement cookie persisting interceptors. /// @@ -79,13 +79,13 @@ abstract class CookieInterceptor implements HttpInterceptor { } /// A HttpInterceptor persisting cookies in the provided [cookieStore]. -@internal final class CookieStoreInterceptor extends CookieInterceptor { /// Creates a new interceptor persisting cookies. const CookieStoreInterceptor({ required this.cookieStore, }); + /// The cookie store instance backing this interceptor. final CookieStore cookieStore; @override diff --git a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/http_interceptor.dart b/packages/interceptor_http_client/lib/src/interceptors/http_interceptor.dart similarity index 90% rename from packages/neon_framework/packages/neon_http_client/lib/src/interceptors/http_interceptor.dart rename to packages/interceptor_http_client/lib/src/interceptors/http_interceptor.dart index 7f8318dee78..e846667201b 100644 --- a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/http_interceptor.dart +++ b/packages/interceptor_http_client/lib/src/interceptors/http_interceptor.dart @@ -14,7 +14,7 @@ abstract interface class HttpInterceptor { /// /// Exceptions might be thrown during interception. /// If the exception is an [http.ClientException] it will be thrown as is, - /// otherwise it wrapped as an `InterceptionException`. + /// otherwise it is wrapped as an `InterceptionException`. FutureOr interceptRequest({required http.BaseRequest request}); /// Whether this interceptor should intercept response. @@ -26,7 +26,7 @@ abstract interface class HttpInterceptor { /// /// Exceptions might be thrown during interception. /// If the exception is an [http.ClientException] it will be thrown as is, - /// otherwise it wrapped as an `InterceptionException`. + /// otherwise it is wrapped as an `InterceptionException`. FutureOr interceptResponse({ required http.StreamedResponse response, required Uri url, diff --git a/packages/interceptor_http_client/lib/src/interceptors/interceptors.dart b/packages/interceptor_http_client/lib/src/interceptors/interceptors.dart new file mode 100644 index 00000000000..20b2373b314 --- /dev/null +++ b/packages/interceptor_http_client/lib/src/interceptors/interceptors.dart @@ -0,0 +1,2 @@ +export 'cookie_interceptor.dart'; +export 'http_interceptor.dart'; diff --git a/packages/interceptor_http_client/pubspec.yaml b/packages/interceptor_http_client/pubspec.yaml new file mode 100644 index 00000000000..a6225cf8b4f --- /dev/null +++ b/packages/interceptor_http_client/pubspec.yaml @@ -0,0 +1,29 @@ +name: interceptor_http_client +description: A http client with request and response interceptors. +version: 0.1.0 +publish_to: none + +environment: + sdk: ^3.0.0 + +dependencies: + built_collection: ^5.0.0 + cookie_store: + git: + url: https://github.com/nextcloud/neon + path: packages/cookie_store + http: ^1.0.0 + meta: ^1.0.0 + +dev_dependencies: + http_client_conformance_tests: + git: + url: https://github.com/dart-lang/http + path: pkgs/http_client_conformance_tests + ref: 76512c4cbf987361421030349fd1946e63e33359 + mocktail: ^1.0.4 + neon_lints: + git: + url: https://github.com/nextcloud/neon + path: packages/neon_lints + test: ^1.25.8 diff --git a/packages/interceptor_http_client/pubspec_overrides.yaml b/packages/interceptor_http_client/pubspec_overrides.yaml new file mode 100644 index 00000000000..d949192bd0c --- /dev/null +++ b/packages/interceptor_http_client/pubspec_overrides.yaml @@ -0,0 +1,6 @@ +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_lints,nextcloud +dependency_overrides: + cookie_store: + path: ../cookie_store + neon_lints: + path: ../neon_lints diff --git a/packages/interceptor_http_client/test/client_conformance_test.dart b/packages/interceptor_http_client/test/client_conformance_test.dart new file mode 100644 index 00000000000..b03bbcfd954 --- /dev/null +++ b/packages/interceptor_http_client/test/client_conformance_test.dart @@ -0,0 +1,44 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:http/http.dart' as http; +import 'package:http_client_conformance_tests/http_client_conformance_tests.dart'; +import 'package:interceptor_http_client/src/interceptor_http_client.dart'; +import 'package:test/test.dart'; + +void main() { + group( + 'Interceptor Client VM conformance test', + () { + testAll( + () => InterceptorHttpClient( + baseClient: http.Client(), + interceptors: BuiltList(), + ), + canReceiveSetCookieHeaders: true, + canSendCookieHeaders: true, + ); + }, + onPlatform: const { + 'browser': [Skip()], + }, + ); + + group( + 'Interceptor Client browser conformance test', + () { + testAll( + () => InterceptorHttpClient( + baseClient: http.Client(), + interceptors: BuiltList(), + ), + redirectAlwaysAllowed: true, + canStreamRequestBody: false, + canStreamResponseBody: false, + canWorkInIsolates: false, + supportsMultipartRequest: false, + ); + }, + onPlatform: const { + 'dart-vm': [Skip()], + }, + ); +} diff --git a/packages/interceptor_http_client/test/interceptor_http_client_test.dart b/packages/interceptor_http_client/test/interceptor_http_client_test.dart new file mode 100644 index 00000000000..bcbe2ee469c --- /dev/null +++ b/packages/interceptor_http_client/test/interceptor_http_client_test.dart @@ -0,0 +1,133 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:interceptor_http_client/interceptor_http_client.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +class _MockInterceptor extends Mock implements HttpInterceptor {} + +class _FakeUri extends Fake implements Uri {} + +void main() { + final uri = Uri.parse('http://example.com'); + Request fakeRequest() { + return Request('PUT', uri); + } + + StreamedResponse fakeResponse() { + return StreamedResponse(const Stream.empty(), 200); + } + + final mockedClient = MockClient((request) async { + return Response.fromStream(fakeResponse()); + }); + late InterceptorHttpClient client; + + setUpAll(() { + registerFallbackValue(_FakeUri()); + registerFallbackValue(fakeResponse()); + registerFallbackValue(fakeRequest()); + }); + + tearDown(() { + client.close(); + }); + + group(InterceptorHttpClient, () { + group('interceptors', () { + late HttpInterceptor interceptor; + + setUp(() { + interceptor = _MockInterceptor(); + + client = InterceptorHttpClient( + baseClient: mockedClient, + interceptors: BuiltList([interceptor]), + ); + }); + + test('does not intercept', () async { + when(() => interceptor.shouldInterceptRequest(any())).thenReturn(false); + when(() => interceptor.shouldInterceptResponse(any())).thenReturn(false); + + final request = Request('GET', uri); + await client.send(request); + + verifyNever( + () => interceptor.interceptRequest( + request: any(named: 'request'), + ), + ); + verifyNever( + () => interceptor.interceptResponse( + response: any(named: 'response'), + url: any(named: 'url'), + ), + ); + }); + + test('does intercept', () async { + when(() => interceptor.shouldInterceptRequest(any())).thenReturn(true); + when(() => interceptor.shouldInterceptResponse(any())).thenReturn(true); + when( + () => interceptor.interceptRequest(request: any(named: 'request')), + ).thenReturn(fakeRequest()); + when( + () => interceptor.interceptResponse(response: any(named: 'response'), url: any(named: 'url')), + ).thenReturn(fakeResponse()); + + final request = Request('GET', uri); + await client.send(request); + + verify( + () => interceptor.interceptRequest( + request: any(named: 'request', that: equals(request)), + ), + ).called(1); + verify( + () => interceptor.interceptResponse( + response: any(named: 'response'), + url: any(named: 'url', that: equals(uri)), + ), + ).called(1); + }); + + test('rethrows errors as InterceptionFailure', () async { + when(() => interceptor.shouldInterceptRequest(any())).thenReturn(true); + when( + () => interceptor.interceptRequest(request: any(named: 'request')), + ).thenThrow(StateError('message')); + + expect( + client.get(uri), + throwsA( + isA().having( + (e) => e.uri, + 'uri', + uri, + ), + ), + ); + + when(() => interceptor.shouldInterceptRequest(any())).thenReturn(true); + when(() => interceptor.interceptRequest(request: any(named: 'request'))).thenReturn(fakeRequest()); + when(() => interceptor.shouldInterceptResponse(any())).thenReturn(true); + when( + () => interceptor.interceptResponse(response: any(named: 'response'), url: any(named: 'url')), + ).thenThrow(StateError('message')); + + expect( + client.get(uri), + throwsA( + isA().having( + (e) => e.uri, + 'uri', + uri, + ), + ), + ); + }); + }); + }); +} diff --git a/packages/neon_framework/packages/neon_http_client/test/interceptors/cookie_interceptor_test.dart b/packages/interceptor_http_client/test/interceptors/cookie_interceptor_test.dart similarity index 96% rename from packages/neon_framework/packages/neon_http_client/test/interceptors/cookie_interceptor_test.dart rename to packages/interceptor_http_client/test/interceptors/cookie_interceptor_test.dart index 32b6baab5df..3ab4de79c22 100644 --- a/packages/neon_framework/packages/neon_http_client/test/interceptors/cookie_interceptor_test.dart +++ b/packages/interceptor_http_client/test/interceptors/cookie_interceptor_test.dart @@ -3,10 +3,9 @@ import 'dart:io'; import 'package:cookie_store/cookie_store.dart'; import 'package:http/http.dart' as http; +import 'package:interceptor_http_client/src/interceptors/cookie_interceptor.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:neon_http_client/src/interceptors/cookie_interceptor.dart'; import 'package:test/test.dart'; -import 'package:universal_io/io.dart'; class _MockCookieStore extends Mock implements CookieStore {} diff --git a/packages/neon_framework/packages/neon_http_client/test/interceptors/http_interceptor_test.dart b/packages/interceptor_http_client/test/interceptors/http_interceptor_test.dart similarity index 90% rename from packages/neon_framework/packages/neon_http_client/test/interceptors/http_interceptor_test.dart rename to packages/interceptor_http_client/test/interceptors/http_interceptor_test.dart index 94fcd21c31b..db1977128bf 100644 --- a/packages/neon_framework/packages/neon_http_client/test/interceptors/http_interceptor_test.dart +++ b/packages/interceptor_http_client/test/interceptors/http_interceptor_test.dart @@ -1,5 +1,5 @@ import 'package:http/http.dart' as http; -import 'package:neon_http_client/src/interceptors/http_interceptor.dart'; +import 'package:interceptor_http_client/src/interceptors/http_interceptor.dart'; import 'package:test/test.dart'; class _TestHttpInterceptor implements HttpInterceptor { diff --git a/packages/neon_framework/example/pubspec.lock b/packages/neon_framework/example/pubspec.lock index 638c34b89e5..942a1a00200 100644 --- a/packages/neon_framework/example/pubspec.lock +++ b/packages/neon_framework/example/pubspec.lock @@ -694,6 +694,13 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1+1" + interceptor_http_client: + dependency: "direct overridden" + description: + path: "../../interceptor_http_client" + relative: true + source: path + version: "0.1.0" intersperse: dependency: transitive description: diff --git a/packages/neon_framework/example/pubspec_overrides.yaml b/packages/neon_framework/example/pubspec_overrides.yaml index dc30aa82b52..54f35f0f5b8 100644 --- a/packages/neon_framework/example/pubspec_overrides.yaml +++ b/packages/neon_framework/example/pubspec_overrides.yaml @@ -1,4 +1,4 @@ -# melos_managed_dependency_overrides: cookie_store,dashboard_app,dynamite_runtime,files_app,neon_framework,neon_http_client,neon_lints,news_app,nextcloud,notes_app,notifications_app,sort_box,talk_app +# melos_managed_dependency_overrides: cookie_store,dashboard_app,dynamite_runtime,files_app,interceptor_http_client,neon_framework,neon_http_client,neon_lints,news_app,nextcloud,notes_app,notifications_app,sort_box,talk_app dependency_overrides: cookie_store: path: ../../cookie_store @@ -8,6 +8,8 @@ dependency_overrides: path: ../../dynamite/packages/dynamite_runtime files_app: path: ../packages/files_app + interceptor_http_client: + path: ../../interceptor_http_client neon_framework: path: .. neon_http_client: diff --git a/packages/neon_framework/packages/account_repository/pubspec_overrides.yaml b/packages/neon_framework/packages/account_repository/pubspec_overrides.yaml index 198db33e28c..5c2f161f46d 100644 --- a/packages/neon_framework/packages/account_repository/pubspec_overrides.yaml +++ b/packages/neon_framework/packages/account_repository/pubspec_overrides.yaml @@ -1,9 +1,11 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box dependency_overrides: cookie_store: path: ../../../cookie_store dynamite_runtime: path: ../../../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../../../interceptor_http_client neon_framework: path: ../.. neon_http_client: diff --git a/packages/neon_framework/packages/dashboard_app/pubspec_overrides.yaml b/packages/neon_framework/packages/dashboard_app/pubspec_overrides.yaml index 198db33e28c..5c2f161f46d 100644 --- a/packages/neon_framework/packages/dashboard_app/pubspec_overrides.yaml +++ b/packages/neon_framework/packages/dashboard_app/pubspec_overrides.yaml @@ -1,9 +1,11 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box dependency_overrides: cookie_store: path: ../../../cookie_store dynamite_runtime: path: ../../../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../../../interceptor_http_client neon_framework: path: ../.. neon_http_client: diff --git a/packages/neon_framework/packages/files_app/pubspec_overrides.yaml b/packages/neon_framework/packages/files_app/pubspec_overrides.yaml index 198db33e28c..5c2f161f46d 100644 --- a/packages/neon_framework/packages/files_app/pubspec_overrides.yaml +++ b/packages/neon_framework/packages/files_app/pubspec_overrides.yaml @@ -1,9 +1,11 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box dependency_overrides: cookie_store: path: ../../../cookie_store dynamite_runtime: path: ../../../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../../../interceptor_http_client neon_framework: path: ../.. neon_http_client: diff --git a/packages/neon_framework/packages/neon_http_client/lib/neon_http_client.dart b/packages/neon_framework/packages/neon_http_client/lib/neon_http_client.dart index 0d62224bb87..db46b718758 100644 --- a/packages/neon_framework/packages/neon_http_client/lib/neon_http_client.dart +++ b/packages/neon_framework/packages/neon_http_client/lib/neon_http_client.dart @@ -3,6 +3,8 @@ /// It allows to register interceptors for requests and responses. library; -export 'src/interceptors/interceptors.dart' show CookieInterceptor, HttpInterceptor; +export 'package:interceptor_http_client/interceptor_http_client.dart' + show CookieInterceptor, HttpInterceptor, InterceptionException, InterceptorHttpClientException; + export 'src/neon_http_client.dart'; export 'src/utils/utils.dart' show kDefaultTimeout; diff --git a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/authorization_throttling_interceptor.dart b/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/authorization_throttling_interceptor.dart index 4e813f21df9..77a5cd74c0d 100644 --- a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/authorization_throttling_interceptor.dart +++ b/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/authorization_throttling_interceptor.dart @@ -1,6 +1,6 @@ import 'package:http/http.dart'; +import 'package:interceptor_http_client/interceptor_http_client.dart'; import 'package:meta/meta.dart'; -import 'package:neon_http_client/src/interceptors/http_interceptor.dart'; import 'package:nextcloud/nextcloud.dart'; @internal diff --git a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/base_header_interceptor.dart b/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/base_header_interceptor.dart index 5b949a5a1a8..b04d34260d4 100644 --- a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/base_header_interceptor.dart +++ b/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/base_header_interceptor.dart @@ -1,6 +1,6 @@ import 'package:http/http.dart' as http; +import 'package:interceptor_http_client/interceptor_http_client.dart'; import 'package:meta/meta.dart'; -import 'package:neon_http_client/src/interceptors/http_interceptor.dart'; /// A HttpInterceptor that adds the given [baseHeaders] to a request. /// diff --git a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/csrf_interceptor.dart b/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/csrf_interceptor.dart index 449028e4253..248bf0de11d 100644 --- a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/csrf_interceptor.dart +++ b/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/csrf_interceptor.dart @@ -1,12 +1,12 @@ import 'dart:convert'; +import 'dart:io'; import 'package:http/http.dart' as http; +import 'package:interceptor_http_client/interceptor_http_client.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'package:neon_http_client/src/interceptors/http_interceptor.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/webdav.dart' as webdav; -import 'package:universal_io/io.dart'; /// A HttpInterceptor that works around a Nextcloud CSRF bug when cookies are sent. /// diff --git a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/interceptors.dart b/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/interceptors.dart index 33d7284ee18..bb9d9836d17 100644 --- a/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/interceptors.dart +++ b/packages/neon_framework/packages/neon_http_client/lib/src/interceptors/interceptors.dart @@ -1,4 +1,3 @@ +export 'authorization_throttling_interceptor.dart'; export 'base_header_interceptor.dart'; -export 'cookie_interceptor.dart'; export 'csrf_interceptor.dart'; -export 'http_interceptor.dart'; diff --git a/packages/neon_framework/packages/neon_http_client/lib/src/neon_http_client.dart b/packages/neon_framework/packages/neon_http_client/lib/src/neon_http_client.dart index 1318011b168..cb663f3c900 100644 --- a/packages/neon_framework/packages/neon_http_client/lib/src/neon_http_client.dart +++ b/packages/neon_framework/packages/neon_http_client/lib/src/neon_http_client.dart @@ -1,33 +1,21 @@ import 'dart:async'; +import 'dart:io' show HttpHeaders; import 'package:built_collection/built_collection.dart'; import 'package:cookie_store/cookie_store.dart'; import 'package:http/http.dart' as http; -import 'package:meta/meta.dart'; -import 'package:neon_http_client/src/interceptors/authorization_throttling_interceptor.dart'; +import 'package:interceptor_http_client/interceptor_http_client.dart'; import 'package:neon_http_client/src/interceptors/interceptors.dart'; import 'package:neon_http_client/src/utils/utils.dart'; -import 'package:universal_io/io.dart'; - -/// An exception caused by an error in a [NeonHttpClient]. -sealed class NeonHttpClientException extends http.ClientException { - NeonHttpClientException(super.message, [super.uri]); -} - -/// An exception caused by a [HttpInterceptor]. -final class InterceptionException extends NeonHttpClientException { - /// Creates a new interceptor failure exception. - InterceptionException(super.message, [super.uri]); -} /// An exception caused by a timed out http request. -final class HttpTimeoutException extends NeonHttpClientException { +final class HttpTimeoutException extends InterceptorHttpClientException { /// Creates a new exception that the request has timed out. HttpTimeoutException(super.message, [super.uri]); } /// A http client for the Neon framework. -final class NeonHttpClient with http.BaseClient { +final class NeonHttpClient extends InterceptorHttpClient { /// Creates a new Neon http client. /// /// @@ -80,84 +68,20 @@ final class NeonHttpClient with http.BaseClient { } const NeonHttpClient._({ - required http.Client baseClient, - required this.interceptors, + required super.baseClient, + required super.interceptors, Duration? timeLimit, - }) : _baseClient = baseClient, - _timeLimit = timeLimit; - - final http.Client _baseClient; - - /// The list of enabled interceptors. - @visibleForTesting - final BuiltList interceptors; + }) : _timeLimit = timeLimit; /// Stop waiting for a response after timeLimit has passed. final Duration? _timeLimit; @override Future send(http.BaseRequest request) async { - var interceptedRequest = request; - for (final interceptor in interceptors) { - if (interceptor.shouldInterceptRequest(interceptedRequest)) { - try { - interceptedRequest = await interceptor.interceptRequest( - request: interceptedRequest, - ); - } catch (error, stackTrace) { - if (error is http.ClientException) { - rethrow; - } - - Error.throwWithStackTrace( - InterceptionException('Failed to intercept request', request.url), - stackTrace, - ); - } - } - } - - http.StreamedResponse interceptedResponse; try { - interceptedResponse = await _baseClient.send(interceptedRequest).maybeTimeout(_timeLimit); + return await super.send(request).maybeTimeout(_timeLimit); } on TimeoutException catch (error) { throw HttpTimeoutException(error.toString(), request.url); } - - Uri url; - if (interceptedResponse case http.BaseResponseWithUrl(url: final responseUrl)) { - url = responseUrl; - } else { - url = interceptedRequest.url; - } - - for (final interceptor in interceptors) { - if (interceptor.shouldInterceptResponse(interceptedResponse)) { - try { - interceptedResponse = await interceptor.interceptResponse( - response: interceptedResponse, - url: url, - ); - } catch (error, stackTrace) { - if (error is http.ClientException) { - rethrow; - } - - Error.throwWithStackTrace( - InterceptionException('Failed to intercept response', request.url), - stackTrace, - ); - } - } - } - - return interceptedResponse; - } - - @override - void close() { - _baseClient.close(); - - super.close(); } } diff --git a/packages/neon_framework/packages/neon_http_client/pubspec.yaml b/packages/neon_framework/packages/neon_http_client/pubspec.yaml index fd9f31c3773..67cc70ad4a1 100644 --- a/packages/neon_framework/packages/neon_http_client/pubspec.yaml +++ b/packages/neon_framework/packages/neon_http_client/pubspec.yaml @@ -13,10 +13,13 @@ dependencies: url: https://github.com/nextcloud/neon path: packages/cookie_store http: ^1.0.0 + interceptor_http_client: + git: + url: https://github.com/nextcloud/neon + path: packages/interceptor_http_client logging: ^1.0.0 meta: ^1.0.0 nextcloud: ^7.0.0 - universal_io: ^2.0.0 dev_dependencies: http_client_conformance_tests: diff --git a/packages/neon_framework/packages/neon_http_client/pubspec_overrides.yaml b/packages/neon_framework/packages/neon_http_client/pubspec_overrides.yaml index 9b0f16df821..84a24926562 100644 --- a/packages/neon_framework/packages/neon_http_client/pubspec_overrides.yaml +++ b/packages/neon_framework/packages/neon_http_client/pubspec_overrides.yaml @@ -4,6 +4,8 @@ dependency_overrides: path: ../../../cookie_store dynamite_runtime: path: ../../../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../../../interceptor_http_client neon_lints: path: ../../../neon_lints nextcloud: diff --git a/packages/neon_framework/packages/neon_http_client/test/client_conformance_test.dart b/packages/neon_framework/packages/neon_http_client/test/client_conformance_test.dart index d0c3c6b560c..0d2faf89b8f 100644 --- a/packages/neon_framework/packages/neon_http_client/test/client_conformance_test.dart +++ b/packages/neon_framework/packages/neon_http_client/test/client_conformance_test.dart @@ -1,15 +1,36 @@ -@TestOn('vm') -@Skip() -library; - import 'package:http_client_conformance_tests/http_client_conformance_tests.dart'; import 'package:neon_http_client/src/neon_http_client.dart'; import 'package:test/test.dart'; void main() { - testAll( - () => NeonHttpClient(baseURL: Uri()), - canReceiveSetCookieHeaders: true, - canSendCookieHeaders: true, + group( + 'Neon Client VM conformance test', + () { + testAll( + () => NeonHttpClient(baseURL: Uri()), + canReceiveSetCookieHeaders: true, + canSendCookieHeaders: true, + ); + }, + onPlatform: const { + 'browser': [Skip()], + }, + ); + + group( + 'Neon Client browser conformance test', + () { + testAll( + () => NeonHttpClient(baseURL: Uri()), + redirectAlwaysAllowed: true, + canStreamRequestBody: false, + canStreamResponseBody: false, + canWorkInIsolates: false, + supportsMultipartRequest: false, + ); + }, + onPlatform: const { + 'dart-vm': [Skip()], + }, ); } diff --git a/packages/neon_framework/packages/neon_http_client/test/neon_http_client_test.dart b/packages/neon_framework/packages/neon_http_client/test/neon_http_client_test.dart index d5bcf428ce4..e0ee86ea486 100644 --- a/packages/neon_framework/packages/neon_http_client/test/neon_http_client_test.dart +++ b/packages/neon_framework/packages/neon_http_client/test/neon_http_client_test.dart @@ -1,8 +1,8 @@ import 'package:cookie_store/cookie_store.dart'; import 'package:http/http.dart'; import 'package:http/testing.dart'; +import 'package:interceptor_http_client/interceptor_http_client.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:neon_http_client/src/interceptors/authorization_throttling_interceptor.dart'; import 'package:neon_http_client/src/interceptors/interceptors.dart'; import 'package:neon_http_client/src/neon_http_client.dart'; import 'package:nextcloud/nextcloud.dart'; diff --git a/packages/neon_framework/packages/news_app/pubspec_overrides.yaml b/packages/neon_framework/packages/news_app/pubspec_overrides.yaml index 198db33e28c..5c2f161f46d 100644 --- a/packages/neon_framework/packages/news_app/pubspec_overrides.yaml +++ b/packages/neon_framework/packages/news_app/pubspec_overrides.yaml @@ -1,9 +1,11 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box dependency_overrides: cookie_store: path: ../../../cookie_store dynamite_runtime: path: ../../../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../../../interceptor_http_client neon_framework: path: ../.. neon_http_client: diff --git a/packages/neon_framework/packages/notes_app/pubspec_overrides.yaml b/packages/neon_framework/packages/notes_app/pubspec_overrides.yaml index 198db33e28c..5c2f161f46d 100644 --- a/packages/neon_framework/packages/notes_app/pubspec_overrides.yaml +++ b/packages/neon_framework/packages/notes_app/pubspec_overrides.yaml @@ -1,9 +1,11 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box dependency_overrides: cookie_store: path: ../../../cookie_store dynamite_runtime: path: ../../../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../../../interceptor_http_client neon_framework: path: ../.. neon_http_client: diff --git a/packages/neon_framework/packages/notifications_app/pubspec_overrides.yaml b/packages/neon_framework/packages/notifications_app/pubspec_overrides.yaml index 198db33e28c..5c2f161f46d 100644 --- a/packages/neon_framework/packages/notifications_app/pubspec_overrides.yaml +++ b/packages/neon_framework/packages/notifications_app/pubspec_overrides.yaml @@ -1,9 +1,11 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box dependency_overrides: cookie_store: path: ../../../cookie_store dynamite_runtime: path: ../../../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../../../interceptor_http_client neon_framework: path: ../.. neon_http_client: diff --git a/packages/neon_framework/packages/talk_app/pubspec_overrides.yaml b/packages/neon_framework/packages/talk_app/pubspec_overrides.yaml index 198db33e28c..5c2f161f46d 100644 --- a/packages/neon_framework/packages/talk_app/pubspec_overrides.yaml +++ b/packages/neon_framework/packages/talk_app/pubspec_overrides.yaml @@ -1,9 +1,11 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_framework,neon_http_client,neon_lints,nextcloud,sort_box dependency_overrides: cookie_store: path: ../../../cookie_store dynamite_runtime: path: ../../../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../../../interceptor_http_client neon_framework: path: ../.. neon_http_client: diff --git a/packages/neon_framework/pubspec_overrides.yaml b/packages/neon_framework/pubspec_overrides.yaml index 624e0aaa28c..568e695dc68 100644 --- a/packages/neon_framework/pubspec_overrides.yaml +++ b/packages/neon_framework/pubspec_overrides.yaml @@ -1,4 +1,4 @@ -# melos_managed_dependency_overrides: cookie_store,cookie_store_conformance_tests,dynamite_runtime,neon_http_client,neon_lints,nextcloud,sort_box +# melos_managed_dependency_overrides: cookie_store,cookie_store_conformance_tests,dynamite_runtime,interceptor_http_client,neon_http_client,neon_lints,nextcloud,sort_box dependency_overrides: cookie_store: path: ../cookie_store @@ -6,6 +6,8 @@ dependency_overrides: path: ../cookie_store/packages/cookie_store_conformance_tests dynamite_runtime: path: ../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../interceptor_http_client neon_http_client: path: packages/neon_http_client neon_lints: diff --git a/packages/nextcloud/packages/nextcloud_test/pubspec_overrides.yaml b/packages/nextcloud/packages/nextcloud_test/pubspec_overrides.yaml index 0c3e5c59f56..7700aab4d4b 100644 --- a/packages/nextcloud/packages/nextcloud_test/pubspec_overrides.yaml +++ b/packages/nextcloud/packages/nextcloud_test/pubspec_overrides.yaml @@ -1,9 +1,11 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_http_client,neon_lints,nextcloud +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_http_client,neon_lints,nextcloud dependency_overrides: cookie_store: path: ../../../cookie_store dynamite_runtime: path: ../../../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../../../interceptor_http_client neon_http_client: path: ../../../neon_framework/packages/neon_http_client neon_lints: diff --git a/packages/nextcloud/pubspec_overrides.yaml b/packages/nextcloud/pubspec_overrides.yaml index a6b97546cd8..5f17d383195 100644 --- a/packages/nextcloud/pubspec_overrides.yaml +++ b/packages/nextcloud/pubspec_overrides.yaml @@ -1,4 +1,4 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite,dynamite_runtime,neon_http_client,neon_lints,nextcloud_test +# melos_managed_dependency_overrides: cookie_store,dynamite,dynamite_runtime,interceptor_http_client,neon_http_client,neon_lints,nextcloud_test dependency_overrides: cookie_store: path: ../cookie_store @@ -6,6 +6,8 @@ dependency_overrides: path: ../dynamite dynamite_runtime: path: ../dynamite/packages/dynamite_runtime + interceptor_http_client: + path: ../interceptor_http_client neon_http_client: path: ../neon_framework/packages/neon_http_client neon_lints: From 47b3d61266f15e40788355ab984fd3acaf14a4a0 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Thu, 5 Sep 2024 10:32:44 +0200 Subject: [PATCH 2/2] refactor(nextcloud_test): drop dependency on neon_http_client and use fixture interceptor Signed-off-by: Nikolas Rimikis --- .../pubspec_overrides.yaml | 2 +- .../neon_http_client/pubspec_overrides.yaml | 2 +- .../lib/src/fixture_interceptor.dart | 96 +++++++++++++++++++ .../lib/src/proxy_http_client.dart | 69 ------------- .../lib/src/test_target/test_target.dart | 19 ++-- .../packages/nextcloud_test/pubspec.yaml | 6 +- .../nextcloud_test/pubspec_overrides.yaml | 4 +- packages/nextcloud/pubspec_overrides.yaml | 4 +- 8 files changed, 114 insertions(+), 88 deletions(-) create mode 100644 packages/nextcloud/packages/nextcloud_test/lib/src/fixture_interceptor.dart delete mode 100644 packages/nextcloud/packages/nextcloud_test/lib/src/proxy_http_client.dart diff --git a/packages/interceptor_http_client/pubspec_overrides.yaml b/packages/interceptor_http_client/pubspec_overrides.yaml index d949192bd0c..8c5a77e9f0c 100644 --- a/packages/interceptor_http_client/pubspec_overrides.yaml +++ b/packages/interceptor_http_client/pubspec_overrides.yaml @@ -1,4 +1,4 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_lints,nextcloud +# melos_managed_dependency_overrides: cookie_store,neon_lints dependency_overrides: cookie_store: path: ../cookie_store diff --git a/packages/neon_framework/packages/neon_http_client/pubspec_overrides.yaml b/packages/neon_framework/packages/neon_http_client/pubspec_overrides.yaml index 84a24926562..e7eb2fb277e 100644 --- a/packages/neon_framework/packages/neon_http_client/pubspec_overrides.yaml +++ b/packages/neon_framework/packages/neon_http_client/pubspec_overrides.yaml @@ -1,4 +1,4 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,neon_lints,nextcloud +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_lints,nextcloud dependency_overrides: cookie_store: path: ../../../cookie_store diff --git a/packages/nextcloud/packages/nextcloud_test/lib/src/fixture_interceptor.dart b/packages/nextcloud/packages/nextcloud_test/lib/src/fixture_interceptor.dart new file mode 100644 index 00000000000..c169fb80175 --- /dev/null +++ b/packages/nextcloud/packages/nextcloud_test/lib/src/fixture_interceptor.dart @@ -0,0 +1,96 @@ +import 'dart:convert'; +import 'dart:io' show HttpHeaders; +import 'dart:typed_data'; + +import 'package:http/http.dart' as http; +import 'package:interceptor_http_client/interceptor_http_client.dart'; + +/// An http interceptor that records every request and adds them to a fixture. +final class FixtureInterceptor implements HttpInterceptor { + /// Creates a new fixture interceptor. + const FixtureInterceptor({ + required this.appendFixture, + }); + + /// Callback for adding a recorded request to the fixture. + final void Function(String fixture) appendFixture; + + @override + bool shouldInterceptRequest(http.BaseRequest request) { + // TODO: use resetFixture and intercept all requests + return request.url.path != '/index.php/csrftoken'; + } + + @override + Future interceptRequest({required http.BaseRequest request}) async { + assert( + shouldInterceptRequest(request), + 'Request should not be intercepted.', + ); + + final bodyBytes = switch (request) { + http.Request() => request.bodyBytes, + _ => await request.finalize().toBytes(), + }; + + final fixture = _formatHttpRequest(request, bodyBytes); + appendFixture(fixture); + + return switch (request) { + http.Request() => request, + _ => http.Request(request.method, request.url) + ..persistentConnection = request.persistentConnection + ..followRedirects = request.followRedirects + ..maxRedirects = request.maxRedirects + ..headers.addAll(request.headers) + ..bodyBytes = bodyBytes, + }; + } + + @override + bool shouldInterceptResponse(http.StreamedResponse response) { + return false; + } + + @override + Never interceptResponse({required http.StreamedResponse response, required Uri url}) { + throw UnsupportedError('Fixtures may not intercept responses.'); + } + + static String _formatHttpRequest(http.BaseRequest request, Uint8List body) { + final buffer = StringBuffer('${request.method.toUpperCase()} ${request.url.replace(port: 80)}'); + + final headers = []; + for (final header in request.headers.entries) { + final name = header.key.toLowerCase(); + var value = header.value; + + if (name == HttpHeaders.hostHeader) { + continue; + } else if (name == HttpHeaders.cookieHeader) { + continue; + } else if (name == HttpHeaders.authorizationHeader) { + value = '${value.split(' ').first} mock'; + } else if (name == 'requesttoken') { + value = 'token'; + } else if (name == 'destination') { + value = Uri.parse(value).replace(port: 80).toString(); + } + + headers.add('\n$name: $value'); + } + + headers.sort(); + buffer.writeAll(headers); + + if (body.isNotEmpty) { + try { + buffer.write('\n${utf8.decode(body)}'); + } catch (_) { + buffer.write('\n${base64.encode(body)}'); + } + } + + return buffer.toString(); + } +} diff --git a/packages/nextcloud/packages/nextcloud_test/lib/src/proxy_http_client.dart b/packages/nextcloud/packages/nextcloud_test/lib/src/proxy_http_client.dart deleted file mode 100644 index 9aaa36ab4f8..00000000000 --- a/packages/nextcloud/packages/nextcloud_test/lib/src/proxy_http_client.dart +++ /dev/null @@ -1,69 +0,0 @@ -// ignore_for_file: invalid_use_of_visible_for_testing_member - -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:http/http.dart'; -import 'package:http/testing.dart'; -import 'package:universal_io/io.dart'; - -/// Gets a mocked [HttpClient] that proxies the request to a real [HttpClient]. -/// For every requests it calls [onRequest] which contains the formatted request. -BaseClient getProxyHttpClient({ - required void Function(String fixture) onRequest, -}) { - final realClient = Client(); - return MockClient.streaming((baseRequest, bytesStream) async { - final bodyBytes = await bytesStream.toBytes(); - if (baseRequest.url.path != '/index.php/csrftoken') { - final fixture = _formatHttpRequest(baseRequest, bodyBytes); - onRequest(fixture); - } - - final request = Request(baseRequest.method, baseRequest.url) - ..persistentConnection = baseRequest.persistentConnection - ..followRedirects = baseRequest.followRedirects - ..maxRedirects = baseRequest.maxRedirects - ..headers.addAll(baseRequest.headers) - ..bodyBytes = bodyBytes; - - return realClient.send(request); - }); -} - -String _formatHttpRequest(BaseRequest request, Uint8List body) { - final buffer = StringBuffer('${request.method.toUpperCase()} ${request.url.replace(port: 80)}'); - - final headers = []; - for (final header in request.headers.entries) { - final name = header.key.toLowerCase(); - var value = header.value; - - if (name == HttpHeaders.hostHeader) { - continue; - } else if (name == HttpHeaders.cookieHeader) { - continue; - } else if (name == HttpHeaders.authorizationHeader) { - value = '${value.split(' ').first} mock'; - } else if (name == 'requesttoken') { - value = 'token'; - } else if (name == 'destination') { - value = Uri.parse(value).replace(port: 80).toString(); - } - - headers.add('\n$name: $value'); - } - - headers.sort(); - buffer.writeAll(headers); - - if (body.isNotEmpty) { - try { - buffer.write('\n${utf8.decode(body)}'); - } catch (_) { - buffer.write('\n${base64.encode(body)}'); - } - } - - return buffer.toString(); -} diff --git a/packages/nextcloud/packages/nextcloud_test/lib/src/test_target/test_target.dart b/packages/nextcloud/packages/nextcloud_test/lib/src/test_target/test_target.dart index 560f5b78d37..1ea74e3e67d 100644 --- a/packages/nextcloud/packages/nextcloud_test/lib/src/test_target/test_target.dart +++ b/packages/nextcloud/packages/nextcloud_test/lib/src/test_target/test_target.dart @@ -3,12 +3,13 @@ import 'dart:io'; import 'package:built_collection/built_collection.dart'; import 'package:cookie_store/cookie_store.dart'; +import 'package:http/http.dart' as http; +import 'package:interceptor_http_client/interceptor_http_client.dart'; import 'package:meta/meta.dart'; -import 'package:neon_http_client/neon_http_client.dart'; import 'package:nextcloud/nextcloud.dart'; +import 'package:nextcloud_test/src/fixture_interceptor.dart'; import 'package:nextcloud_test/src/fixtures.dart'; import 'package:nextcloud_test/src/models/models.dart'; -import 'package:nextcloud_test/src/proxy_http_client.dart'; import 'package:nextcloud_test/src/test_target/docker_container.dart'; import 'package:nextcloud_test/src/test_target/local.dart'; import 'package:version/version.dart'; @@ -74,12 +75,14 @@ abstract class TestTargetInstance { appPassword = await createAppPassword(username); } - final httpClient = NeonHttpClient( - baseURL: hostURL, - cookieStore: CookieStore(), - client: getProxyHttpClient( - onRequest: appendFixture, - ), + final httpClient = InterceptorHttpClient( + baseClient: http.Client(), + interceptors: BuiltList([ + CookieStoreInterceptor( + cookieStore: CookieStore(), + ), + const FixtureInterceptor(appendFixture: appendFixture), + ]), ); return NextcloudClient( diff --git a/packages/nextcloud/packages/nextcloud_test/pubspec.yaml b/packages/nextcloud/packages/nextcloud_test/pubspec.yaml index 9de5a57e03f..65f0f9f9043 100644 --- a/packages/nextcloud/packages/nextcloud_test/pubspec.yaml +++ b/packages/nextcloud/packages/nextcloud_test/pubspec.yaml @@ -13,11 +13,11 @@ dependencies: path: packages/cookie_store glob: ^2.1.2 http: ^1.2.0 - meta: ^1.0.0 - neon_http_client: + interceptor_http_client: git: url: https://github.com/nextcloud/neon - path: packages/neon_http_client + path: packages/interceptor_http_client + meta: ^1.0.0 nextcloud: ^7.0.0 path: ^1.9.0 process_run: ^1.0.0+1 diff --git a/packages/nextcloud/packages/nextcloud_test/pubspec_overrides.yaml b/packages/nextcloud/packages/nextcloud_test/pubspec_overrides.yaml index 7700aab4d4b..cfe67bc0b7e 100644 --- a/packages/nextcloud/packages/nextcloud_test/pubspec_overrides.yaml +++ b/packages/nextcloud/packages/nextcloud_test/pubspec_overrides.yaml @@ -1,4 +1,4 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_http_client,neon_lints,nextcloud +# melos_managed_dependency_overrides: cookie_store,dynamite_runtime,interceptor_http_client,neon_lints,nextcloud dependency_overrides: cookie_store: path: ../../../cookie_store @@ -6,8 +6,6 @@ dependency_overrides: path: ../../../dynamite/packages/dynamite_runtime interceptor_http_client: path: ../../../interceptor_http_client - neon_http_client: - path: ../../../neon_framework/packages/neon_http_client neon_lints: path: ../../../neon_lints nextcloud: diff --git a/packages/nextcloud/pubspec_overrides.yaml b/packages/nextcloud/pubspec_overrides.yaml index 5f17d383195..d31cf22a4a6 100644 --- a/packages/nextcloud/pubspec_overrides.yaml +++ b/packages/nextcloud/pubspec_overrides.yaml @@ -1,4 +1,4 @@ -# melos_managed_dependency_overrides: cookie_store,dynamite,dynamite_runtime,interceptor_http_client,neon_http_client,neon_lints,nextcloud_test +# melos_managed_dependency_overrides: cookie_store,dynamite,dynamite_runtime,interceptor_http_client,neon_lints,nextcloud_test dependency_overrides: cookie_store: path: ../cookie_store @@ -8,8 +8,6 @@ dependency_overrides: path: ../dynamite/packages/dynamite_runtime interceptor_http_client: path: ../interceptor_http_client - neon_http_client: - path: ../neon_framework/packages/neon_http_client neon_lints: path: ../neon_lints nextcloud_test: