From 92f408776548ba8e0469c0ceab8f6e1621a44986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 22 Oct 2025 19:44:00 +0200 Subject: [PATCH 1/6] ci: Use dart-lang/setup-dart@v1 --- .github/workflows/dart-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart-tests.yaml b/.github/workflows/dart-tests.yaml index ae6baf3c..f9c54953 100644 --- a/.github/workflows/dart-tests.yaml +++ b/.github/workflows/dart-tests.yaml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Dart - uses: dart-lang/setup-dart@v1.3 + uses: dart-lang/setup-dart@v1 with: sdk: ${{ matrix.dart_sdk }} From 85383f67124ea2023bbe8a9957dd66ed57cf53d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 23 Oct 2025 09:44:27 +0200 Subject: [PATCH 2/6] chore: Bump sdk version to 3.7 --- .github/workflows/dart-tests.yaml | 8 ++++---- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dart-tests.yaml b/.github/workflows/dart-tests.yaml index f9c54953..6d621795 100644 --- a/.github/workflows/dart-tests.yaml +++ b/.github/workflows/dart-tests.yaml @@ -11,7 +11,7 @@ on: env: PUB_CACHE_PATH: ~/.pub-cache - LOWEST_DART_SDK: "3.5.0" + LOWEST_DART_SDK: "3.7.0" jobs: build: @@ -23,7 +23,7 @@ jobs: dart_sdk: # ${{ env.LOWEST_DART_SDK }} won't work at job level as env context not available for strategy ¯\_(ツ)_/¯ # (see https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#context-availability) - - "3.5.0" + - "3.7.0" - stable steps: - name: Checkout Code @@ -82,12 +82,12 @@ jobs: unit_tests: name: Run Unit Tests runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.dart_sdk != '3.3.0' }} # env context not available for continue-on-error + continue-on-error: ${{ matrix.dart_sdk != '3.7.0' }} # env context not available for continue-on-error strategy: fail-fast: false matrix: os: [windows-latest, ubuntu-latest, macos-latest] - dart_sdk: ["3.5.0", stable, beta] # env context not available for strategy + dart_sdk: ["3.7.0", stable, beta] # env context not available for strategy deps: [downgrade, upgrade] steps: - name: Checkout Code diff --git a/pubspec.yaml b/pubspec.yaml index f171cd78..5e62b0c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.8.0 repository: https://github.com/serverpod/relic environment: - sdk: ^3.5.0 + sdk: ^3.7.0 dependencies: async: ^2.11.0 From ef2f454f359be125b1fcad840ad981c0dd740444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 23 Oct 2025 09:45:49 +0200 Subject: [PATCH 3/6] chore: Reformat after sdk bump. Also _ is no longer a variable. --- README.md | 2 +- benchmark/benchmark.dart | 167 ++-- example/advanced/multi_isolate.dart | 14 +- example/advanced/static_files_example.dart | 67 +- example/basic/as_handle.dart | 18 +- example/basic/body_example.dart | 105 +-- example/context/context_example.dart | 78 +- example/context/context_property_example.dart | 13 +- example/example.dart | 19 +- example/middleware/auth_example.dart | 28 +- example/middleware/cors_example.dart | 36 +- example/middleware/example.dart | 24 +- example/middleware/middleware_example.dart | 38 +- example/middleware/pipeline_example.dart | 30 +- example/routing/basic_routing.dart | 26 +- example/routing/request_example.dart | 126 +-- example/routing/response_example.dart | 10 +- lib/src/adapter/adapter.dart | 8 +- .../adapter/io/http_response_extension.dart | 16 +- lib/src/adapter/io/io_adapter.dart | 13 +- lib/src/adapter/io/io_relic_web_socket.dart | 39 +- lib/src/adapter/io/io_serve.dart | 16 +- lib/src/adapter/io/response.dart | 6 +- lib/src/body/body.dart | 13 +- lib/src/body/types/body_type.dart | 5 +- lib/src/handler/cascade.dart | 28 +- lib/src/handler/handler.dart | 10 +- .../headers/codecs/common_types_codecs.dart | 12 +- .../headers/exception/header_exception.dart | 7 +- .../extension/string_list_extensions.dart | 18 +- lib/src/headers/header_accessor.dart | 6 +- lib/src/headers/headers.dart | 317 ++++--- lib/src/headers/mutable_headers.dart | 2 +- .../headers/standard_headers_extensions.dart | 9 +- .../typed/headers/accept_encoding_header.dart | 8 +- .../headers/typed/headers/accept_header.dart | 18 +- .../typed/headers/accept_language_header.dart | 8 +- .../typed/headers/accept_ranges_header.dart | 5 +- .../access_control_allow_headers_header.dart | 15 +- .../access_control_allow_methods_header.dart | 5 +- .../access_control_allow_origin_header.dart | 21 +- .../access_control_expose_headers_header.dart | 21 +- .../typed/headers/authentication_header.dart | 25 +- .../typed/headers/authorization_header.dart | 42 +- .../typed/headers/cache_control_header.dart | 67 +- .../typed/headers/clear_site_data_header.dart | 24 +- .../typed/headers/connection_header.dart | 15 +- .../headers/content_disposition_header.dart | 26 +- .../headers/content_encoding_header.dart | 15 +- .../headers/content_language_header.dart | 22 +- .../typed/headers/content_range_header.dart | 19 +- .../content_security_policy_header.dart | 43 +- .../headers/typed/headers/cookie_header.dart | 20 +- .../cross_origin_embedder_policy_header.dart | 16 +- .../cross_origin_opener_policy_header.dart | 16 +- .../cross_origin_resource_policy_header.dart | 11 +- .../headers/typed/headers/etag_header.dart | 5 +- .../typed/headers/forwarded_header.dart | 57 +- .../headers/typed/headers/from_header.dart | 4 +- .../typed/headers/if_range_header.dart | 5 +- .../headers/permission_policy_header.dart | 58 +- .../headers/typed/headers/range_header.dart | 41 +- .../typed/headers/referrer_policy_header.dart | 20 +- .../typed/headers/retry_after_header.dart | 14 +- .../typed/headers/sec_fetch_dest_header.dart | 5 +- .../typed/headers/sec_fetch_mode_header.dart | 5 +- .../typed/headers/sec_fetch_site_header.dart | 5 +- .../typed/headers/set_cookie_header.dart | 36 +- .../strict_transport_security_header.dart | 11 +- lib/src/headers/typed/headers/te_header.dart | 37 +- .../headers/transfer_encoding_header.dart | 16 +- .../headers/typed/headers/upgrade_header.dart | 27 +- .../typed/headers/util/cookie_util.dart | 34 +- .../headers/typed/headers/vary_header.dart | 7 +- .../typed/headers/wildcard_list_header.dart | 5 +- .../typed/headers/x_forwarded_for_header.dart | 7 +- lib/src/io/static/cache_busting_config.dart | 20 +- lib/src/io/static/static_handler.dart | 166 ++-- lib/src/isolated_object.dart | 96 +- lib/src/logger/logger.dart | 22 +- lib/src/message/message.dart | 10 +- lib/src/message/request.dart | 88 +- lib/src/message/response.dart | 142 ++- lib/src/middleware/context_property.dart | 5 +- lib/src/middleware/middleware_logger.dart | 52 +- lib/src/middleware/routing_middleware.dart | 25 +- lib/src/relic_server.dart | 74 +- lib/src/router/method.dart | 2 +- lib/src/router/path_trie.dart | 65 +- lib/src/router/relic_app.dart | 7 +- lib/src/router/router.dart | 24 +- lib/src/router/router_handler_extension.dart | 6 +- lib/src/util/util.dart | 21 +- test/body/body_infer_mime_type_test.dart | 61 +- test/body/types/body_type_test.dart | 6 +- test/exception/relic_exceptions_test.dart | 34 +- test/handler/cascade_test.dart | 343 ++++--- test/handler/pipeline_test.dart | 91 +- ...control_allow_credentials_header_test.dart | 228 +++-- .../access_control_max_age_header_test.dart | 231 +++-- ...s_control_request_headers_header_test.dart | 255 +++-- test/headers/basic/age_header_test.dart | 311 +++---- test/headers/basic/allow_header_test.dart | 211 ++--- .../basic/content_location_header_test.dart | 261 +++--- test/headers/basic/date_header_test.dart | 233 +++-- test/headers/basic/expires_header_test.dart | 240 +++-- test/headers/basic/host_header_test.dart | 629 ++++++------- .../basic/if_modified_since_header_test.dart | 44 +- .../if_unmodified_since_header_test.dart | 265 +++--- test/headers/basic/last_modified_header.dart | 129 ++- test/headers/basic/location_header_test.dart | 279 +++--- .../basic/max_forwards_header_test.dart | 245 +++-- test/headers/basic/origin_header_test.dart | 296 +++--- test/headers/basic/referer_header_test.dart | 267 +++--- test/headers/basic/server_header_test.dart | 170 ++-- test/headers/basic/trailer_header_test.dart | 42 +- .../headers/basic/user_agent_header_test.dart | 83 +- test/headers/basic/via_header_test.dart | 102 +- .../basic/x_powered_by_header_test.dart | 85 +- test/headers/header_test.dart | 875 ++++++++++-------- test/headers/headers_accessor_test.dart | 75 +- test/headers/headers_constants_test.dart | 22 +- test/headers/headers_test_utils.dart | 8 +- test/headers/mutable_headers_test.dart | 19 +- .../typed/accept_encoding_header_test.dart | 253 +++-- test/headers/typed/accept_header_test.dart | 111 ++- test/headers/typed/accept_language_test.dart | 599 ++++++------ .../typed/accept_ranges_header_test.dart | 56 +- ...ess_control_allow_headers_header_test.dart | 51 +- ...ess_control_allow_methods_header_test.dart | 45 +- ...cess_control_allow_origin_header_test.dart | 34 +- ...ss_control_expose_headers_header_test.dart | 98 +- ...ss_control_request_method_header_test.dart | 89 +- .../typed/authorization_header_test.dart | 714 +++++++------- .../typed/cache_control_header_test.dart | 46 +- .../typed/clear_site_data_header_test.dart | 80 +- .../headers/typed/connection_header_test.dart | 154 ++- .../content_disposition_header_test.dart | 68 +- .../typed/content_encoding_header_test.dart | 98 +- .../typed/content_language_header_test.dart | 66 +- .../typed/content_range_header_test.dart | 81 +- .../content_security_policy_header_test.dart | 68 +- test/headers/typed/cookie_header_test.dart | 25 +- ...ss_origin_embedder_policy_header_test.dart | 193 ++-- ...ross_origin_opener_policy_header_test.dart | 45 +- ...ss_origin_resource_policy_header_test.dart | 200 ++-- test/headers/typed/etag_header_test.dart | 100 +- test/headers/typed/expect_header_test.dart | 96 +- .../typed/forwarded_header_behavior_test.dart | 32 +- test/headers/typed/forwarded_header_test.dart | 437 +++++---- test/headers/typed/from_header_test.dart | 141 ++- test/headers/typed/if_match_header_test.dart | 209 ++--- .../typed/if_none_match_header_test.dart | 147 ++- test/headers/typed/if_range_header_test.dart | 81 +- .../typed/permissions_policy_header_test.dart | 59 +- .../typed/proxy_authenticate_header_test.dart | 53 +- .../proxy_authorization_header_test.dart | 596 ++++++------ test/headers/typed/range_header_test.dart | 160 ++-- .../typed/referrer_policy_header_test.dart | 68 +- .../typed/retry_after_header_test.dart | 73 +- .../typed/sec_fetch_dest_header_test.dart | 66 +- .../typed/sec_fetch_mode_header_test.dart | 66 +- .../typed/sec_fetch_site_header_test.dart | 66 +- .../headers/typed/set_cookie_header_test.dart | 55 +- ...strict_transport_security_header_test.dart | 296 +++--- test/headers/typed/te_header_test.dart | 276 +++--- .../typed/transfer_encoding_header_test.dart | 291 +++--- test/headers/typed/upgrade_header_test.dart | 90 +- test/headers/typed/vary_header_test.dart | 85 +- .../typed/www_authenticate_header_test.dart | 61 +- .../typed/x_forwarded_for_header_test.dart | 170 ++-- test/hijack/relic_hijack_test.dart | 33 +- .../isolated_object_close_test.dart | 6 +- .../isolated_object_create_test.dart | 9 +- .../isolated_object_evaluate_test.dart | 80 +- test/message/apply_headers_test.dart | 105 +-- test/message/message_test.dart | 116 ++- test/message/request_test.dart | 512 ++++++---- test/message/response_test.dart | 227 ++--- test/middleware/create_middleware_test.dart | 427 +++++---- test/middleware/log_middleware_test.dart | 68 +- test/middleware/middleware_object_test.dart | 12 +- test/middleware/routing_middleware_test.dart | 655 +++++++------ test/relic_server_serve_test.dart | 272 +++--- test/relic_server_test.dart | 14 +- test/router/lru_cache_test.dart | 24 +- test/router/normalized_path_test.dart | 110 +-- test/router/path_trie_crud_test.dart | 270 +++--- test/router/path_trie_tail_test.dart | 211 +++-- test/router/path_trie_test.dart | 198 ++-- test/router/path_trie_use_test.dart | 74 +- test/router/path_trie_wildcard_test.dart | 85 +- test/router/relic_app_test.dart | 23 +- test/router/router_groups_test.dart | 18 +- test/router/router_handler_test.dart | 80 +- test/router/router_inject_test.dart | 103 ++- test/router/router_methods_test.dart | 178 ++-- test/router/router_test.dart | 205 ++-- test/router/router_use_test.dart | 89 +- test/src/adapter/context_test.dart | 221 +++-- .../src/middleware/context_property_test.dart | 277 +++--- test/static/alternative_root_test.dart | 140 +-- test/static/basic_file_test.dart | 415 +++++---- test/static/cache_busting_config_test.dart | 614 ++++++------ .../cache_busting_static_handler_test.dart | 575 +++++++----- test/static/cache_control_test.dart | 88 +- test/static/content_type_test.dart | 102 +- test/static/create_file_handler_test.dart | 238 ++--- test/static/default_handler_test.dart | 65 +- test/static/get_handler_test.dart | 82 +- test/static/if_modified_since_test.dart | 52 +- test/static/if_none_match_test.dart | 135 +-- test/static/if_range_test.dart | 154 +-- test/static/mounted_test.dart | 12 +- test/static/not_found_test.dart | 81 +- test/static/range_edge_cases_test.dart | 378 ++++---- test/static/range_test.dart | 238 +++-- test/static/symbolic_link_test.dart | 69 +- test/static/test_util.dart | 22 +- test/static/unsupported_methods_test.dart | 171 ++-- test/util/test_util.dart | 42 +- test/util/util_test.dart | 20 +- test/web_socket/web_socket_test.dart | 584 ++++++------ 223 files changed, 12721 insertions(+), 12358 deletions(-) diff --git a/README.md b/README.md index d36f7fdd..8b8ec22a 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Future main() async { final router = RelicRouter() ..get('/user/:name/age/:age', hello) ..use('/', logRequests()) - ..fallback = respondWith((final _) => Response.notFound( + ..fallback = respondWith((_) => Response.notFound( body: Body.fromString("Sorry, that doesn't compute"))); // RelicRouter can be used directly as a handler via the call() extension diff --git a/benchmark/benchmark.dart b/benchmark/benchmark.dart index 100dc3d2..cd744e7f 100644 --- a/benchmark/benchmark.dart +++ b/benchmark/benchmark.dart @@ -22,20 +22,22 @@ late final List dynamicRoutesToLookup; void setupBenchmarkData(final int routeCount) { logger.info('Setting up benchmark data with $routeCount routes...'); indexes = List.generate(routeCount, (final i) => i); - final permutedIndexes = indexes.toList() - ..shuffle(Random(123)); // Use fixed seed for reproducibility + final permutedIndexes = + indexes.toList() + ..shuffle(Random(123)); // Use fixed seed for reproducibility // Pre-generate lookup paths staticRoutesToLookup = permutedIndexes.map((final i) => '/path$i').toList(); - dynamicRoutesToLookup = permutedIndexes - .map( - (final i) => - // Fixed seed for reproducibility - '/users/user_${Random(i).nextInt(1000)}' - '/items/item_${Random(i + 1).nextInt(5000)}' - '/profile$i', - ) - .toList(); + dynamicRoutesToLookup = + permutedIndexes + .map( + (final i) => + // Fixed seed for reproducibility + '/users/user_${Random(i).nextInt(1000)}' + '/items/item_${Random(i + 1).nextInt(5000)}' + '/profile$i', + ) + .toList(); logger.info('Setup complete.'); } @@ -65,7 +67,7 @@ class Emitter extends ScoreEmitterV2 { abstract class RouterBenchmark extends PerfBenchmarkBase { RouterBenchmark(final Iterable grouping, final Emitter emitter) - : super(grouping.join(';'), emitter: emitter); + : super(grouping.join(';'), emitter: emitter); @override void exercise() => run(); @@ -74,7 +76,7 @@ abstract class RouterBenchmark extends PerfBenchmarkBase { // Benchmark for adding static routes class StaticAddBenchmark extends RouterBenchmark { StaticAddBenchmark(final Emitter emitter) - : super(['Add', 'Static', 'x$routeCount', 'Router'], emitter); + : super(['Add', 'Static', 'x$routeCount', 'Router'], emitter); @override void run() { @@ -88,7 +90,7 @@ class StaticAddBenchmark extends RouterBenchmark { // Benchmark for looking up static routes class StaticLookupBenchmark extends RouterBenchmark { StaticLookupBenchmark(final Emitter emitter) - : super(['Lookup', 'Static', 'x$routeCount', 'Router'], emitter); + : super(['Lookup', 'Static', 'x$routeCount', 'Router'], emitter); late final Router router; @@ -112,7 +114,7 @@ class StaticLookupBenchmark extends RouterBenchmark { // Benchmark for adding dynamic routes class DynamicAddBenchmark extends RouterBenchmark { DynamicAddBenchmark(final Emitter emitter) - : super(['Add', 'Dynamic', 'x$routeCount', 'Router'], emitter); + : super(['Add', 'Dynamic', 'x$routeCount', 'Router'], emitter); @override void run() { @@ -126,7 +128,7 @@ class DynamicAddBenchmark extends RouterBenchmark { // Benchmark for looking up dynamic routes class DynamicLookupBenchmark extends RouterBenchmark { DynamicLookupBenchmark(final Emitter emitter) - : super(['Lookup', 'Dynamic', 'x$routeCount', 'Router'], emitter); + : super(['Lookup', 'Dynamic', 'x$routeCount', 'Router'], emitter); late final Router router; @@ -150,7 +152,7 @@ class DynamicLookupBenchmark extends RouterBenchmark { class StaticAddRoutingkitBenchmark extends RouterBenchmark { StaticAddRoutingkitBenchmark(final Emitter emitter) - : super(['Add', 'Static', 'x$routeCount', 'Routingkit'], emitter); + : super(['Add', 'Static', 'x$routeCount', 'Routingkit'], emitter); @override void run() { @@ -163,7 +165,7 @@ class StaticAddRoutingkitBenchmark extends RouterBenchmark { class StaticLookupRoutingkitBenchmark extends RouterBenchmark { StaticLookupRoutingkitBenchmark(final Emitter emitter) - : super(['Lookup', 'Static', 'x$routeCount', 'Routingkit'], emitter); + : super(['Lookup', 'Static', 'x$routeCount', 'Routingkit'], emitter); late final routingkit.Router router; @@ -186,7 +188,7 @@ class StaticLookupRoutingkitBenchmark extends RouterBenchmark { class DynamicAddRoutingkitBenchmark extends RouterBenchmark { DynamicAddRoutingkitBenchmark(final Emitter emitter) - : super(['Add', 'Dynamic', 'x$routeCount', 'Routingkit'], emitter); + : super(['Add', 'Dynamic', 'x$routeCount', 'Routingkit'], emitter); @override void run() { @@ -199,7 +201,7 @@ class DynamicAddRoutingkitBenchmark extends RouterBenchmark { class DynamicLookupRoutingkitBenchmark extends RouterBenchmark { DynamicLookupRoutingkitBenchmark(final Emitter emitter) - : super(['Lookup', 'Dynamic', 'x$routeCount', 'Routingkit'], emitter); + : super(['Lookup', 'Dynamic', 'x$routeCount', 'Routingkit'], emitter); late final routingkit.Router router; @@ -223,7 +225,7 @@ class DynamicLookupRoutingkitBenchmark extends RouterBenchmark { class StaticAddSpannerBenchmark extends RouterBenchmark { StaticAddSpannerBenchmark(final Emitter emitter) - : super(['Add', 'Static', 'x$routeCount', 'Spanner'], emitter); + : super(['Add', 'Static', 'x$routeCount', 'Spanner'], emitter); @override void run() { @@ -236,7 +238,7 @@ class StaticAddSpannerBenchmark extends RouterBenchmark { class StaticLookupSpannerBenchmark extends RouterBenchmark { StaticLookupSpannerBenchmark(final Emitter emitter) - : super(['Lookup', 'Static', 'x$routeCount', 'Spanner'], emitter); + : super(['Lookup', 'Static', 'x$routeCount', 'Spanner'], emitter); late final spanner.Spanner router; @@ -259,21 +261,24 @@ class StaticLookupSpannerBenchmark extends RouterBenchmark { class DynamicAddSpannerBenchmark extends RouterBenchmark { DynamicAddSpannerBenchmark(final Emitter emitter) - : super(['Add', 'Dynamic', 'x$routeCount', 'Spanner'], emitter); + : super(['Add', 'Dynamic', 'x$routeCount', 'Spanner'], emitter); @override void run() { final router = spanner.Spanner(); for (final i in indexes) { router.addRoute( - spanner.HTTPMethod.GET, '/users//items//profile$i', i); + spanner.HTTPMethod.GET, + '/users//items//profile$i', + i, + ); } } } class DynamicLookupSpannerBenchmark extends RouterBenchmark { DynamicLookupSpannerBenchmark(final Emitter emitter) - : super(['Lookup', 'Dynamic', 'x$routeCount', 'Spanner'], emitter); + : super(['Lookup', 'Dynamic', 'x$routeCount', 'Spanner'], emitter); late final spanner.Spanner router; @@ -283,7 +288,10 @@ class DynamicLookupSpannerBenchmark extends RouterBenchmark { router = spanner.Spanner(); for (final i in indexes) { router.addRoute( - spanner.HTTPMethod.GET, '/users//items//profile$i', i); + spanner.HTTPMethod.GET, + '/users//items//profile$i', + i, + ); } } @@ -297,36 +305,44 @@ class DynamicLookupSpannerBenchmark extends RouterBenchmark { } enum RunOption implements OptionDefinition { - file(FileOption( - argName: 'output', - argAbbrev: 'o', - helpText: 'The file to write benchmark results to', - fromDefault: _defaultFile, - mode: PathExistMode.mustNotExist, - )), - - iterations(IntOption( - argName: 'iterations', - argAbbrev: 'i', - helpText: 'Something to do with scale', - defaultsTo: 1000, - min: 1, - )), - - storeInNotes(FlagOption( - argName: 'store-in-git-notes', - argAbbrev: 's', - helpText: 'Store benchmark result with git notes', - defaultsTo: false, - )), - - pause(FlagOption( - argName: 'pause-on-startup', - argAbbrev: 'p', - helpText: 'Pause on startup to allow devtools to attach', - defaultsTo: false, - hideNegatedUsage: true, - )); + file( + FileOption( + argName: 'output', + argAbbrev: 'o', + helpText: 'The file to write benchmark results to', + fromDefault: _defaultFile, + mode: PathExistMode.mustNotExist, + ), + ), + + iterations( + IntOption( + argName: 'iterations', + argAbbrev: 'i', + helpText: 'Something to do with scale', + defaultsTo: 1000, + min: 1, + ), + ), + + storeInNotes( + FlagOption( + argName: 'store-in-git-notes', + argAbbrev: 's', + helpText: 'Store benchmark result with git notes', + defaultsTo: false, + ), + ), + + pause( + FlagOption( + argName: 'pause-on-startup', + argAbbrev: 'p', + helpText: 'Pause on startup to allow devtools to attach', + defaultsTo: false, + hideNegatedUsage: true, + ), + ); const RunOption(this.option); @@ -384,10 +400,14 @@ class RunCommand extends BetterCommand, void> { if (storeInNotes) { final head = await git.commitFromRevision('HEAD'); logger.info('Appending benchmark results to: ${head.treeSha} (tree)'); - await git.runCommand( - ['notes', '--ref=benchmarks', 'append', '-F', file.path, head.treeSha], - echoOutput: logger.shouldLog(LogLevel.debug), - ); + await git.runCommand([ + 'notes', + '--ref=benchmarks', + 'append', + '-F', + file.path, + head.treeSha, + ], echoOutput: logger.shouldLog(LogLevel.debug)); } } } @@ -419,9 +439,11 @@ class ExtractCommand extends BetterCommand, void> { final to = commandConfig.value(ExtractOption.to); final git = await GitDir.fromExisting(p.current, allowSubdirectory: true); - final result = await git.runCommand( - ['log', '--format=%aI %H %T', '$from..$to'], - ); + final result = await git.runCommand([ + 'log', + '--format=%aI %H %T', + '$from..$to', + ]); final sb = StringBuffer(); for (final line in (result.stdout as String).split('\n')) { @@ -433,10 +455,12 @@ class ExtractCommand extends BetterCommand, void> { final treeSha = hashes[2]; logger.debug('$commitSha $treeSha $authorTime'); - final result = await git.runCommand( - ['notes', '--ref=benchmarks', 'show', treeSha], - throwOnError: false, - ); + final result = await git.runCommand([ + 'notes', + '--ref=benchmarks', + 'show', + treeSha, + ], throwOnError: false); if (result.exitCode == 0) sb.writeln(result.stdout); } logger.info(sb.toString()); @@ -463,13 +487,8 @@ Future main(final List args) async { 'Relic Benchmark Tool', setLogLevel: setLogLevel, enableCompletionCommand: true, - embeddedCompletions: [ - completionScriptCarapace, - ], - )..addCommands([ - RunCommand(), - ExtractCommand(), - ]); + embeddedCompletions: [completionScriptCarapace], + )..addCommands([RunCommand(), ExtractCommand()]); try { await runner.run(args); } on UsageException catch (ex) { diff --git a/example/advanced/multi_isolate.dart b/example/advanced/multi_isolate.dart index 6c5c0f9e..4fdc0e8e 100644 --- a/example/advanced/multi_isolate.dart +++ b/example/advanced/multi_isolate.dart @@ -12,10 +12,13 @@ void main() async { // Wait for all the isolates to spawn log('Starting $isolateCount isolates'); - final isolates = await Future.wait(List.generate( + final isolates = await Future.wait( + List.generate( isolateCount, (final index) => - Isolate.spawn((final _) => _serve(), null, debugName: '$index'))); + Isolate.spawn((final _) => _serve(), null, debugName: '$index'), + ), + ); // Wait for Ctrl-C before proceeding await ProcessSignal.sigint.watch().first; @@ -29,9 +32,10 @@ void main() async { /// [_serve] is called in each spawned isolate. Future _serve() async { // A router with no routes but a fallback - final app = RelicApp() - ..use('/', logRequests()) - ..put('/echo', respondWith(_echoRequest)); + final app = + RelicApp() + ..use('/', logRequests()) + ..put('/echo', respondWith(_echoRequest)); // start the server await app.serve(shared: true); diff --git a/example/advanced/static_files_example.dart b/example/advanced/static_files_example.dart index 5f810e33..a85cd3df 100644 --- a/example/advanced/static_files_example.dart +++ b/example/advanced/static_files_example.dart @@ -15,8 +15,8 @@ Future main() async { '/basic/**', StaticHandler.directory( staticDir, - cacheControl: (final ctx, final fileInfo) => - CacheControlHeader(maxAge: 86400), + cacheControl: + (final ctx, final fileInfo) => CacheControlHeader(maxAge: 86400), ).asHandler, ); @@ -26,8 +26,8 @@ Future main() async { '/logo.svg', StaticHandler.file( File('example/static_files/logo.svg'), - cacheControl: (final ctx, final fileInfo) => - CacheControlHeader(maxAge: 3600), + cacheControl: + (final ctx, final fileInfo) => CacheControlHeader(maxAge: 3600), ).asHandler, ); @@ -38,10 +38,11 @@ Future main() async { '/short-cache/**', StaticHandler.directory( staticDir, - cacheControl: (final ctx, final fileInfo) => CacheControlHeader( - maxAge: 3600, // 1 hour - publicCache: true, // Allow CDN caching - ), + cacheControl: + (final ctx, final fileInfo) => CacheControlHeader( + maxAge: 3600, // 1 hour + publicCache: true, // Allow CDN caching + ), ).asHandler, ); @@ -52,11 +53,12 @@ Future main() async { '/long-cache/**', StaticHandler.directory( staticDir, - cacheControl: (final ctx, final fileInfo) => CacheControlHeader( - maxAge: 31536000, // 1 year - publicCache: true, - immutable: true, // Browser won't revalidate - ), + cacheControl: + (final ctx, final fileInfo) => CacheControlHeader( + maxAge: 31536000, // 1 year + publicCache: true, + immutable: true, // Browser won't revalidate + ), ).asHandler, ); @@ -68,18 +70,22 @@ Future main() async { ); // Index page showing cache-busted URLs - app.get('/', respondWith((final _) async { - final helloUrl = await buster.assetPath('/static/hello.txt'); - final logoUrl = await buster.assetPath('/static/logo.svg'); - final html = '' - '

Static files with cache busting

' - '' - ''; - return Response.ok(body: Body.fromString(html, mimeType: MimeType.html)); - })); + app.get( + '/', + respondWith((final _) async { + final helloUrl = await buster.assetPath('/static/hello.txt'); + final logoUrl = await buster.assetPath('/static/logo.svg'); + final html = + '' + '

Static files with cache busting

' + '' + ''; + return Response.ok(body: Body.fromString(html, mimeType: MimeType.html)); + }), + ); // Serve static files with cache busting app.anyOf( @@ -87,11 +93,12 @@ Future main() async { '/static/**', StaticHandler.directory( staticDir, - cacheControl: (final ctx, final fileInfo) => CacheControlHeader( - maxAge: 31536000, // 1 year - safe with cache busting - publicCache: true, - immutable: true, - ), + cacheControl: + (final ctx, final fileInfo) => CacheControlHeader( + maxAge: 31536000, // 1 year - safe with cache busting + publicCache: true, + immutable: true, + ), cacheBustingConfig: buster, ).asHandler, ); diff --git a/example/basic/as_handle.dart b/example/basic/as_handle.dart index 84f9d273..4cbc2715 100644 --- a/example/basic/as_handle.dart +++ b/example/basic/as_handle.dart @@ -5,14 +5,16 @@ import 'package:relic/relic.dart'; Future main() async { // Create a router that route all request to the same handler - final router = RelicRouter() - ..use('/', logRequests()) // log all request from / and down - ..any( - '/**', - respondWith( - (final request) => Response.ok(body: Body.fromString('Hello, Relic!')), - ), - ); + final router = + RelicRouter() + ..use('/', logRequests()) // log all request from / and down + ..any( + '/**', + respondWith( + (final request) => + Response.ok(body: Body.fromString('Hello, Relic!')), + ), + ); // Start a server that forward request to the handler final server = RelicServer( diff --git a/example/basic/body_example.dart b/example/basic/body_example.dart index 9de8bcc4..392d8d61 100644 --- a/example/basic/body_example.dart +++ b/example/basic/body_example.dart @@ -9,14 +9,13 @@ import 'package:relic/relic.dart'; /// Example demonstrating Body class features. /// Shows creating bodies from strings, files, and streams. Future main() async { - final app = RelicApp() - ..fallback = (final ctx) { - return ctx.respond( - Response.ok( - body: Body.fromString('Body Example'), - ), - ); - }; + final app = + RelicApp() + ..fallback = (final ctx) { + return ctx.respond( + Response.ok(body: Body.fromString('Body Example')), + ); + }; // Basic text response app.get('/hello', helloHandler); @@ -57,11 +56,12 @@ Future main() async { // Single static file serving app.get( - '/logo', - StaticHandler.file( - File('example/static_files/logo.svg'), - cacheControl: (final _, final __) => CacheControlHeader(maxAge: 86400), - ).asHandler); + '/logo', + StaticHandler.file( + File('example/static_files/logo.svg'), + cacheControl: (final _, final __) => CacheControlHeader(maxAge: 86400), + ).asHandler, + ); await app.serve(); log('Server running on http://localhost:8080'); @@ -81,17 +81,17 @@ Future main() async { /// Basic text response handler ResponseContext helloHandler(final NewContext ctx) { - return ctx.respond(Response.ok( - body: Body.fromString('Hello, World!'), - )); + return ctx.respond(Response.ok(body: Body.fromString('Hello, World!'))); } /// JSON with automatic MIME detection handler ResponseContext dataHandler(final NewContext ctx) { - return ctx.respond(Response.ok( - body: Body.fromString('{"message": "Hello"}'), - // Automatically detects application/json - )); + return ctx.respond( + Response.ok( + body: Body.fromString('{"message": "Hello"}'), + // Automatically detects application/json + ), + ); } /// Small file handler - read entire file into memory @@ -104,9 +104,7 @@ Future smallFileHandler(final NewContext ctx) async { final bytes = await file.readAsBytes(); - return ctx.respond(Response.ok( - body: Body.fromData(bytes), - )); + return ctx.respond(Response.ok(body: Body.fromData(bytes))); } /// Large file handler - stream for memory efficiency @@ -124,21 +122,16 @@ Future largeFileHandler(final NewContext ctx) async { final fileStream = file.openRead().map((final e) => Uint8List.fromList(e)); final fileSize = await file.length(); - return ctx.respond(Response.ok( - body: Body.fromDataStream( - fileStream, - contentLength: fileSize, - ), - )); + return ctx.respond( + Response.ok(body: Body.fromDataStream(fileStream, contentLength: fileSize)), + ); } /// Reading request body as string handler Future echoHandler(final NewContext ctx) async { final content = await ctx.request.readAsString(); - return ctx.respond(Response.ok( - body: Body.fromString('You sent: $content'), - )); + return ctx.respond(Response.ok(body: Body.fromString('You sent: $content'))); } /// JSON API handler @@ -148,12 +141,14 @@ Future apiDataHandler(final NewContext ctx) async { log('Received: $data'); - return ctx.respond(Response.ok( - body: Body.fromString( - jsonEncode({'result': 'success'}), - mimeType: MimeType.json, + return ctx.respond( + Response.ok( + body: Body.fromString( + jsonEncode({'result': 'success'}), + mimeType: MimeType.json, + ), ), - )); + ); } /// File upload handler with size validation @@ -162,9 +157,9 @@ Future uploadHandler(final NewContext ctx) async { final contentLength = ctx.request.body.contentLength; if (contentLength != null && contentLength > maxFileSize) { - return ctx.respond(Response.badRequest( - body: Body.fromString('File too large'), - )); + return ctx.respond( + Response.badRequest(body: Body.fromString('File too large')), + ); } final stream = ctx.request.read(); @@ -172,9 +167,7 @@ Future uploadHandler(final NewContext ctx) async { await file.parent.create(recursive: true); await stream.forEach((final chunk) => file.openWrite().write(chunk)); - return ctx.respond(Response.ok( - body: Body.fromString('Upload successful'), - )); + return ctx.respond(Response.ok(body: Body.fromString('Upload successful'))); } /// Image response handler with automatic format detection @@ -182,12 +175,14 @@ Future imageHandler(final NewContext ctx) async { final file = File('example/static_files/logo.svg'); final imageBytes = await file.readAsBytes(); - return ctx.respond(Response.ok( - body: Body.fromData( - imageBytes, - mimeType: MimeType.parse('image/svg+xml'), + return ctx.respond( + Response.ok( + body: Body.fromData( + imageBytes, + mimeType: MimeType.parse('image/svg+xml'), + ), ), - )); + ); } /// Streaming response handler with chunked transfer encoding @@ -201,11 +196,13 @@ Future streamHandler(final NewContext ctx) async { final dataStream = generateLargeDataset(); - return ctx.respond(Response.ok( - body: Body.fromDataStream( - dataStream, - mimeType: MimeType.json, - // contentLength omitted for chunked encoding + return ctx.respond( + Response.ok( + body: Body.fromDataStream( + dataStream, + mimeType: MimeType.json, + // contentLength omitted for chunked encoding + ), ), - )); + ); } diff --git a/example/context/context_example.dart b/example/context/context_example.dart index 52a56376..48729930 100644 --- a/example/context/context_example.dart +++ b/example/context/context_example.dart @@ -34,13 +34,15 @@ String _htmlHomePage() { } Future homeHandler(final NewContext ctx) async { - return ctx.respond(Response.ok( - body: Body.fromString( - _htmlHomePage(), - encoding: utf8, - mimeType: MimeType.html, + return ctx.respond( + Response.ok( + body: Body.fromString( + _htmlHomePage(), + encoding: utf8, + mimeType: MimeType.html, + ), ), - )); + ); } Future apiHandler(final NewContext ctx) async { @@ -50,12 +52,11 @@ Future apiHandler(final NewContext ctx) async { 'path': ctx.request.url.path, }; - return ctx.respond(Response.ok( - body: Body.fromString( - jsonEncode(data), - mimeType: MimeType.json, + return ctx.respond( + Response.ok( + body: Body.fromString(jsonEncode(data), mimeType: MimeType.json), ), - )); + ); } Future userHandler(final NewContext ctx) async { @@ -66,12 +67,11 @@ Future userHandler(final NewContext ctx) async { 'timestamp': DateTime.now().toIso8601String(), }; - return ctx.respond(Response.ok( - body: Body.fromString( - jsonEncode(data), - mimeType: MimeType.json, + return ctx.respond( + Response.ok( + body: Body.fromString(jsonEncode(data), mimeType: MimeType.json), ), - )); + ); } ConnectContext webSocketHandler(final NewContext ctx) { @@ -103,7 +103,8 @@ HijackContext customProtocolHandler(final NewContext ctx) { log('Connection hijacked for custom protocol'); // Send a custom HTTP response manually - const response = 'HTTP/1.1 200 OK\r\n' + const response = + 'HTTP/1.1 200 OK\r\n' 'Content-Type: text/plain\r\n' 'Connection: close\r\n' '\r\n' @@ -126,8 +127,11 @@ Future dataHandler(final NewContext ctx) async { // Access headers (these are typed accessors from the Headers class) final authHeader = request.headers.authorization; // 'Bearer token123' or null - final contentType = request.body.bodyType - ?.mimeType; // appljson, octet-stream, plainText, etc. or null + final contentType = + request + .body + .bodyType + ?.mimeType; // appljson, octet-stream, plainText, etc. or null log('authHeader: $authHeader, contentType: $contentType'); @@ -137,38 +141,36 @@ Future dataHandler(final NewContext ctx) async { final bodyString = await request.readAsString(); final jsonData = json.decode(bodyString) as Map; - return ctx.respond(Response.ok( - body: Body.fromString('Received: ${jsonData['name']}'), - )); + return ctx.respond( + Response.ok(body: Body.fromString('Received: ${jsonData['name']}')), + ); } catch (e) { return ctx.respond( - Response.badRequest( - body: Body.fromString('Invalid JSON'), - ), + Response.badRequest(body: Body.fromString('Invalid JSON')), ); } } // Return bad request if the content type is not JSON return ctx.respond( - Response.badRequest( - body: Body.fromString('Invalid Request'), - ), + Response.badRequest(body: Body.fromString('Invalid Request')), ); } void main() async { // Set up the router with proper routes - final app = RelicApp() - ..get('/', homeHandler) // Home page - ..get('/api', apiHandler) // Simple API - ..get('/api/users/:id', userHandler) // API with parameters - ..get('/ws', webSocketHandler) // WebSocket - ..get('/custom', customProtocolHandler) // Custom protocol - ..post('/data', dataHandler) // Data handler - ..fallback = respondWith((final request) => Response.notFound( - body: Body.fromString('Page not found'), - )); + final app = + RelicApp() + ..get('/', homeHandler) // Home page + ..get('/api', apiHandler) // Simple API + ..get('/api/users/:id', userHandler) // API with parameters + ..get('/ws', webSocketHandler) // WebSocket + ..get('/custom', customProtocolHandler) // Custom protocol + ..post('/data', dataHandler) // Data handler + ..fallback = respondWith( + (final request) => + Response.notFound(body: Body.fromString('Page not found')), + ); // Start the server await app.serve(); diff --git a/example/context/context_property_example.dart b/example/context/context_property_example.dart index 77c48863..69088342 100644 --- a/example/context/context_property_example.dart +++ b/example/context/context_property_example.dart @@ -24,16 +24,17 @@ Future handler(final NewContext ctx) async { log('Request ID: $requestId'); - return ctx.respond(Response.ok( - body: Body.fromString('Your request ID is: $requestId'), - )); + return ctx.respond( + Response.ok(body: Body.fromString('Your request ID is: $requestId')), + ); } void main() async { // Set up the router with routes - final app = RelicApp() - ..use('/', requestIdMiddleware) // Sets the request ID - ..get('/', handler); // Uses the request ID + final app = + RelicApp() + ..use('/', requestIdMiddleware) // Sets the request ID + ..get('/', handler); // Uses the request ID await app.serve(); log('Server running on http://localhost:8080'); diff --git a/example/example.dart b/example/example.dart index 6051b676..7373c2f0 100644 --- a/example/example.dart +++ b/example/example.dart @@ -4,12 +4,19 @@ import 'package:relic/relic.dart'; /// A simple 'Hello World' server Future main() async { // Setup app - final app = RelicApp() - ..get('/user/:name/age/:age', hello) // route with parameters (:name & :age) - ..use('/', logRequests()) // middleware on all paths below '/' - // custom fallback - optional (default is 404 Not Found) - ..fallback = respondWith((final _) => Response.notFound( - body: Body.fromString("Sorry, that doesn't compute"))); + final app = + RelicApp() + ..get( + '/user/:name/age/:age', + hello, + ) // route with parameters (:name & :age) + ..use('/', logRequests()) // middleware on all paths below '/' + // custom fallback - optional (default is 404 Not Found) + ..fallback = respondWith( + (_) => Response.notFound( + body: Body.fromString("Sorry, that doesn't compute"), + ), + ); // Start the server. Defaults to using port 8080 on loopback interface await app.serve(); diff --git a/example/middleware/auth_example.dart b/example/middleware/auth_example.dart index e6d959b0..7d638e68 100644 --- a/example/middleware/auth_example.dart +++ b/example/middleware/auth_example.dart @@ -10,9 +10,9 @@ Middleware authMiddleware() { final apiKey = ctx.request.headers['X-API-Key']?.first; if (apiKey != 'secret123') { - return ctx.respond(Response.unauthorized( - body: Body.fromString('Invalid API key'), - )); + return ctx.respond( + Response.unauthorized(body: Body.fromString('Invalid API key')), + ); } log('User authenticated with API key'); @@ -23,26 +23,22 @@ Middleware authMiddleware() { /// Public handler (no auth needed) Future publicHandler(final NewContext ctx) async { - return ctx.respond(Response.ok( - body: Body.fromString('This is public!'), - )); + return ctx.respond(Response.ok(body: Body.fromString('This is public!'))); } /// Protected handler (needs auth) Future protectedHandler(final NewContext ctx) async { - return ctx.respond(Response.ok( - body: Body.fromString('This is protected!'), - )); + return ctx.respond(Response.ok(body: Body.fromString('This is protected!'))); } void main() async { - final router = RelicApp() - // Public routes - ..get('/public', publicHandler) - - // Protected routes (with auth middleware) - ..use('/protected', authMiddleware()) - ..get('/protected', protectedHandler); + final router = + RelicApp() + // Public routes + ..get('/public', publicHandler) + // Protected routes (with auth middleware) + ..use('/protected', authMiddleware()) + ..get('/protected', protectedHandler); await router.serve(port: 8080); log('Auth example running on http://localhost:8080'); diff --git a/example/middleware/cors_example.dart b/example/middleware/cors_example.dart index a785f6ae..2346fda5 100644 --- a/example/middleware/cors_example.dart +++ b/example/middleware/cors_example.dart @@ -10,13 +10,15 @@ Middleware corsMiddleware() { return (final NewContext ctx) async { // Handle preflight requests if (ctx.request.method == Method.options) { - return ctx.respond(Response.ok( - headers: Headers.build((final mh) { - mh['Access-Control-Allow-Origin'] = ['*']; - mh['Access-Control-Allow-Methods'] = ['GET, POST, OPTIONS']; - mh['Access-Control-Allow-Headers'] = ['Content-Type']; - }), - )); + return ctx.respond( + Response.ok( + headers: Headers.build((final mh) { + mh['Access-Control-Allow-Origin'] = ['*']; + mh['Access-Control-Allow-Methods'] = ['GET, POST, OPTIONS']; + mh['Access-Control-Allow-Headers'] = ['Content-Type']; + }), + ), + ); } // Process normal request @@ -41,20 +43,20 @@ Middleware corsMiddleware() { Future apiHandler(final NewContext ctx) async { final data = {'message': 'Hello from CORS API!'}; - return ctx.respond(Response.ok( - body: Body.fromString(jsonEncode(data)), - )); + return ctx.respond(Response.ok(body: Body.fromString(jsonEncode(data)))); } void main() async { - final app = RelicApp() - // Apply CORS to all routes - ..use('/', corsMiddleware()) - - // API route - ..get('/api', apiHandler); + final app = + RelicApp() + // Apply CORS to all routes + ..use('/', corsMiddleware()) + // API route + ..get('/api', apiHandler); await app.serve(); log('Simple CORS example running on http://localhost:8080'); - log('Test with: curl -H "Origin: https://example.com" http://localhost:8080/api'); + log( + 'Test with: curl -H "Origin: https://example.com" http://localhost:8080/api', + ); } diff --git a/example/middleware/example.dart b/example/middleware/example.dart index ab7063e1..7310aaf6 100644 --- a/example/middleware/example.dart +++ b/example/middleware/example.dart @@ -8,18 +8,18 @@ import 'package:relic/relic.dart'; Future main() async { // start server - final server = await Isolate.spawn((final _) async { - final app = RelicApp() - ..use('/api', - AuthMiddleware().asMiddleware) // <-- add auth middleware on /api - ..get( - '/api/user/info', - (final ctx) => ctx.respond( - Response.ok( - body: Body.fromString('${ctx.user}'), - ), - ), - ); + final server = await Isolate.spawn((_) async { + final app = + RelicApp() + ..use( + '/api', + AuthMiddleware().asMiddleware, + ) // <-- add auth middleware on /api + ..get( + '/api/user/info', + (final ctx) => + ctx.respond(Response.ok(body: Body.fromString('${ctx.user}'))), + ); await app.serve(); }, null); diff --git a/example/middleware/middleware_example.dart b/example/middleware/middleware_example.dart index f1119bc1..18833a99 100644 --- a/example/middleware/middleware_example.dart +++ b/example/middleware/middleware_example.dart @@ -47,9 +47,11 @@ Middleware errorHandlingMiddleware() { try { return await innerHandler(ctx); } catch (error) { - return ctx.respond(Response.internalServerError( - body: Body.fromString('Something went wrong'), - )); + return ctx.respond( + Response.internalServerError( + body: Body.fromString('Something went wrong'), + ), + ); } }; }; @@ -57,30 +59,28 @@ Middleware errorHandlingMiddleware() { /// Simple handlers Future homeHandler(final NewContext ctx) async { - return ctx.respond(Response.ok( - body: Body.fromString('Hello from home page!'), - )); + return ctx.respond( + Response.ok(body: Body.fromString('Hello from home page!')), + ); } Future apiHandler(final NewContext ctx) async { final data = {'message': 'Hello from API!'}; - return ctx.respond(Response.ok( - body: Body.fromString(jsonEncode(data)), - )); + return ctx.respond(Response.ok(body: Body.fromString(jsonEncode(data)))); } void main() async { - final app = RelicApp() - // Apply middleware to all routes - ..use('/', logRequests()) - ..use('/', timingMiddleware()) - ..use('/', addHeaderMiddleware()) - - // Routes - ..get('/', homeHandler) - ..use('/api', errorHandlingMiddleware()) - ..get('/api', apiHandler); + final app = + RelicApp() + // Apply middleware to all routes + ..use('/', logRequests()) + ..use('/', timingMiddleware()) + ..use('/', addHeaderMiddleware()) + // Routes + ..get('/', homeHandler) + ..use('/api', errorHandlingMiddleware()) + ..get('/api', apiHandler); await app.serve(); log('Middleware example running on http://localhost:8080'); diff --git a/example/middleware/pipeline_example.dart b/example/middleware/pipeline_example.dart index c05fa306..e2613910 100644 --- a/example/middleware/pipeline_example.dart +++ b/example/middleware/pipeline_example.dart @@ -24,9 +24,9 @@ Middleware addServerHeader() { /// Simple handler Future simpleHandler(final NewContext ctx) async { - return ctx.respond(Response.ok( - body: Body.fromString('Hello from Pipeline!'), - )); + return ctx.respond( + Response.ok(body: Body.fromString('Hello from Pipeline!')), + ); } void main() async { @@ -37,19 +37,21 @@ void main() async { .addHandler(simpleHandler); // Using Router (preferred) - final router = RelicApp() - ..use('/', logRequests()) - ..use('/', addServerHeader()) - ..get('/router', (final NewContext ctx) async { - return ctx.respond(Response.ok( - body: Body.fromString('Hello from Router!'), - )); - }); + final router = + RelicApp() + ..use('/', logRequests()) + ..use('/', addServerHeader()) + ..get('/router', (final NewContext ctx) async { + return ctx.respond( + Response.ok(body: Body.fromString('Hello from Router!')), + ); + }); // Main router that shows both approaches - final app = RelicApp() - ..get('/pipeline', pipelineHandler) - ..get('/router', router.asHandler); + final app = + RelicApp() + ..get('/pipeline', pipelineHandler) + ..get('/router', router.asHandler); await app.serve(); log('Pipeline example running on http://localhost:8080'); diff --git a/example/routing/basic_routing.dart b/example/routing/basic_routing.dart index 145ae0b8..4a8205c3 100644 --- a/example/routing/basic_routing.dart +++ b/example/routing/basic_routing.dart @@ -24,37 +24,27 @@ Future main() async { // Convenience methods - syntactic sugar for .add() // Respond with "Hello World!" on the homepage app.get('/', (final ctx) { - return ctx.respond( - Response.ok( - body: Body.fromString('Hello World!'), - ), - ); + return ctx.respond(Response.ok(body: Body.fromString('Hello World!'))); }); // Respond to a POST request on the root route app.post('/', (final ctx) { return ctx.respond( - Response.ok( - body: Body.fromString('Got a POST request'), - ), + Response.ok(body: Body.fromString('Got a POST request')), ); }); // Respond to a PUT request to the /user route app.put('/user', (final ctx) { return ctx.respond( - Response.ok( - body: Body.fromString('Got a PUT request at /user'), - ), + Response.ok(body: Body.fromString('Got a PUT request at /user')), ); }); // Respond to a DELETE request to the /user route app.delete('/user', (final ctx) { return ctx.respond( - Response.ok( - body: Body.fromString('Got a DELETE request at /user'), - ), + Response.ok(body: Body.fromString('Got a DELETE request at /user')), ); }); @@ -62,9 +52,7 @@ Future main() async { // This is what the convenience methods (.get, .post, etc.) call internally app.add(Method.patch, '/api', (final ctx) { return ctx.respond( - Response.ok( - body: Body.fromString('Got a PATCH request at /api'), - ), + Response.ok(body: Body.fromString('Got a PATCH request at /api')), ); }); @@ -72,9 +60,7 @@ Future main() async { app.anyOf({Method.get, Method.post}, '/admin', (final ctx) { final method = ctx.request.method.name.toUpperCase(); return ctx.respond( - Response.ok( - body: Body.fromString('Admin page - $method request'), - ), + Response.ok(body: Body.fromString('Admin page - $method request')), ); }); diff --git a/example/routing/request_example.dart b/example/routing/request_example.dart index f7d8c72d..fb998fc2 100644 --- a/example/routing/request_example.dart +++ b/example/routing/request_example.dart @@ -11,12 +11,12 @@ Future main() async { app.get('/info', (final ctx) { final method = ctx.request.method; // Method.get - return ctx.respond(Response.ok( - body: Body.fromString('Received a ${method.name} request'), - )); + return ctx.respond( + Response.ok(body: Body.fromString('Received a ${method.name} request')), + ); }); -// Path parameters example + // Path parameters example app.get('/users/:id', (final ctx) { final id = ctx.pathParameters[#id]!; final url = ctx.request.url; @@ -34,23 +34,29 @@ Future main() async { final page = ctx.request.url.queryParameters['page']; if (query == null) { - return ctx.respond(Response.badRequest( - body: Body.fromString('Query parameter "query" is required'), - )); + return ctx.respond( + Response.badRequest( + body: Body.fromString('Query parameter "query" is required'), + ), + ); } - return ctx.respond(Response.ok( - body: Body.fromString('Searching for: $query (page: ${page ?? "1"})'), - )); + return ctx.respond( + Response.ok( + body: Body.fromString('Searching for: $query (page: ${page ?? "1"})'), + ), + ); }); // Query parameters - multiple values app.get('/filter', (final ctx) { final tags = ctx.request.url.queryParametersAll['tag'] ?? []; - return ctx.respond(Response.ok( - body: Body.fromString('Filtering by tags: ${tags.join(", ")}'), - )); + return ctx.respond( + Response.ok( + body: Body.fromString('Filtering by tags: ${tags.join(", ")}'), + ), + ); }); // Type-safe headers @@ -62,13 +68,15 @@ Future main() async { final userAgent = request.headers.userAgent; // String? final contentLength = request.headers.contentLength; // int? - return ctx.respond(Response.ok( - body: Body.fromString( - 'Browser: ${userAgent ?? "Unknown"}, ' - 'Content-Type: ${mimeType?.toString() ?? "None"}, ' - 'Content-Length: ${contentLength ?? "Unknown"}', + return ctx.respond( + Response.ok( + body: Body.fromString( + 'Browser: ${userAgent ?? "Unknown"}, ' + 'Content-Type: ${mimeType?.toString() ?? "None"}, ' + 'Content-Length: ${contentLength ?? "Unknown"}', + ), ), - )); + ); }); // Authorization headers @@ -78,17 +86,20 @@ Future main() async { if (auth is BearerAuthorizationHeader) { final token = auth.token; // Validate token... - return ctx.respond(Response.ok( - body: Body.fromString('Bearer token: $token'), - )); + return ctx.respond( + Response.ok(body: Body.fromString('Bearer token: $token')), + ); } else if (auth is BasicAuthorizationHeader) { final username = auth.username; final password = auth.password; // Validate credentials... - return ctx.respond(Response.ok( - body: Body.fromString( - 'Basic auth: $username (password length: ${password.length})'), - )); + return ctx.respond( + Response.ok( + body: Body.fromString( + 'Basic auth: $username (password length: ${password.length})', + ), + ), + ); } else { return ctx.respond(Response.unauthorized()); } @@ -97,9 +108,9 @@ Future main() async { // Reading request body as string app.post('/submit', (final ctx) async { final bodyText = await ctx.request.readAsString(); - return ctx.respond(Response.ok( - body: Body.fromString('Received: $bodyText'), - )); + return ctx.respond( + Response.ok(body: Body.fromString('Received: $bodyText')), + ); }); // JSON parsing example @@ -112,20 +123,22 @@ Future main() async { final email = data['email'] as String?; if (name == null || email == null) { - return ctx.respond(Response.badRequest( - body: Body.fromString('Name and email are required'), - )); + return ctx.respond( + Response.badRequest( + body: Body.fromString('Name and email are required'), + ), + ); } // Process user creation... - return ctx.respond(Response.ok( - body: Body.fromString('User created: $name'), - )); + return ctx.respond( + Response.ok(body: Body.fromString('User created: $name')), + ); } catch (e) { - return ctx.respond(Response.badRequest( - body: Body.fromString('Invalid JSON: $e'), - )); + return ctx.respond( + Response.badRequest(body: Body.fromString('Invalid JSON: $e')), + ); } }); @@ -139,17 +152,17 @@ Future main() async { // Process chunk... } - return ctx.respond(Response.ok( - body: Body.fromString('Uploaded $totalBytes bytes'), - )); + return ctx.respond( + Response.ok(body: Body.fromString('Uploaded $totalBytes bytes')), + ); }); // Check if body is empty app.post('/data', (final ctx) { if (ctx.request.isEmpty) { - return ctx.respond(Response.badRequest( - body: Body.fromString('Request body is required'), - )); + return ctx.respond( + Response.badRequest(body: Body.fromString('Request body is required')), + ); } // Body exists, safe to read... @@ -161,16 +174,18 @@ Future main() async { final pageStr = ctx.request.url.queryParameters['page']; if (pageStr == null) { - return ctx.respond(Response.badRequest( - body: Body.fromString('Page parameter is required'), - )); + return ctx.respond( + Response.badRequest( + body: Body.fromString('Page parameter is required'), + ), + ); } final page = int.tryParse(pageStr); if (page == null || page < 1) { - return ctx.respond(Response.badRequest( - body: Body.fromString('Invalid page number'), - )); + return ctx.respond( + Response.badRequest(body: Body.fromString('Invalid page number')), + ); } // Use validated page number... @@ -181,13 +196,12 @@ Future main() async { app.get('/info', (final ctx) { final userAgent = ctx.request.headers.userAgent; - final message = userAgent != null - ? 'Your browser: $userAgent' - : 'Browser information not available'; + final message = + userAgent != null + ? 'Your browser: $userAgent' + : 'Browser information not available'; - return ctx.respond(Response.ok( - body: Body.fromString(message), - )); + return ctx.respond(Response.ok(body: Body.fromString(message))); }); await app.serve(); diff --git a/example/routing/response_example.dart b/example/routing/response_example.dart index 320e2f14..4169a568 100644 --- a/example/routing/response_example.dart +++ b/example/routing/response_example.dart @@ -8,9 +8,7 @@ Future main() async { // Success response example app.get('/status', (final ctx) { - return ctx.respond(Response.ok( - body: Body.fromString('Status is Ok'), - )); + return ctx.respond(Response.ok(body: Body.fromString('Status is Ok'))); }); // Bad request example @@ -18,9 +16,9 @@ Future main() async { try { throw 'Invalid JSON'; } catch (e) { - return ctx.respond(Response.badRequest( - body: Body.fromString('Invalid JSON'), - )); + return ctx.respond( + Response.badRequest(body: Body.fromString('Invalid JSON')), + ); } }); diff --git a/lib/src/adapter/adapter.dart b/lib/src/adapter/adapter.dart index da907c37..ae71712d 100644 --- a/lib/src/adapter/adapter.dart +++ b/lib/src/adapter/adapter.dart @@ -74,7 +74,9 @@ abstract class Adapter { /// /// For web-sockets see [connect] Future hijack( - final AdapterRequest request, final HijackCallback callback); + final AdapterRequest request, + final HijackCallback callback, + ); /// Establishes a web-socket connection for the given [AdapterRequest]. /// @@ -85,7 +87,9 @@ abstract class Adapter { /// - [callback]: The [WebSocketCallback] that will be invoked on inbound /// connection requests. Future connect( - final AdapterRequest request, final WebSocketCallback callback); + final AdapterRequest request, + final WebSocketCallback callback, + ); /// Gracefully shuts down the adapter. /// diff --git a/lib/src/adapter/io/http_response_extension.dart b/lib/src/adapter/io/http_response_extension.dart index bb2fa1a8..a8e7bcb2 100644 --- a/lib/src/adapter/io/http_response_extension.dart +++ b/lib/src/adapter/io/http_response_extension.dart @@ -61,14 +61,14 @@ extension HttpResponseExtension on io.HttpResponse { /// - Handling multipart/byteranges responses according to their specific requirements. bool _shouldEnableChunkedEncoding(final Body body) { return - // Exclude 1xx status codes (no body allowed). - statusCode >= 200 && - // Exclude 204 (No Content) status code (no body allowed). - statusCode != 204 && - // Exclude 304 (Not Modified) status code (no body allowed). - statusCode != 304 && - // Exclude multipart/byteranges responses (handled via Content-Range). - !body.isMultipartByteranges; + // Exclude 1xx status codes (no body allowed). + statusCode >= 200 && + // Exclude 204 (No Content) status code (no body allowed). + statusCode != 204 && + // Exclude 304 (Not Modified) status code (no body allowed). + statusCode != 304 && + // Exclude multipart/byteranges responses (handled via Content-Range). + !body.isMultipartByteranges; } } diff --git a/lib/src/adapter/io/io_adapter.dart b/lib/src/adapter/io/io_adapter.dart index 4b40041f..5d19d108 100644 --- a/lib/src/adapter/io/io_adapter.dart +++ b/lib/src/adapter/io/io_adapter.dart @@ -49,12 +49,16 @@ class IOAdapter extends Adapter { final bool v6Only = false, final bool shared = false, }) async { - return IOAdapter(await bindHttpServer(address, + return IOAdapter( + await bindHttpServer( + address, port: port, context: context, backlog: backlog, v6Only: v6Only, - shared: shared)); + shared: shared, + ), + ); } /// The [io.InternetAddress] the underlying server is listening on. @@ -80,8 +84,9 @@ class IOAdapter extends Adapter { covariant final IOAdapterRequest request, final HijackCallback callback, ) async { - final socket = - await request._httpRequest.response.detachSocket(writeHeaders: false); + final socket = await request._httpRequest.response.detachSocket( + writeHeaders: false, + ); callback(StreamChannel(socket, socket)); } diff --git a/lib/src/adapter/io/io_relic_web_socket.dart b/lib/src/adapter/io/io_relic_web_socket.dart index f29371d8..a496275f 100644 --- a/lib/src/adapter/io/io_relic_web_socket.dart +++ b/lib/src/adapter/io/io_relic_web_socket.dart @@ -17,12 +17,16 @@ class IORelicWebSocket implements RelicWebSocket { /// If provided, the [protocols] argument indicates that subprotocols that /// the peer is able to select. See /// [RFC-6455 1.9](https://datatracker.ietf.org/doc/html/rfc6455#section-1.9). - static Future connect(final Uri url, - {final Iterable? protocols}) async { + static Future connect( + final Uri url, { + final Iterable? protocols, + }) async { final io.WebSocket webSocket; try { - webSocket = - await io.WebSocket.connect(url.toString(), protocols: protocols); + webSocket = await io.WebSocket.connect( + url.toString(), + protocols: protocols, + ); } on io.WebSocketException catch (e) { throw WebSocketException(e.message); } @@ -33,15 +37,15 @@ class IORelicWebSocket implements RelicWebSocket { // See https://github.com/dart-lang/sdk/issues/55106 await webSocket.close(1002); // protocol error throw WebSocketException( - 'unexpected protocol selected by peer: ${webSocket.protocol}'); + 'unexpected protocol selected by peer: ${webSocket.protocol}', + ); } return IORelicWebSocket._(webSocket); } static Future fromHttpRequest( final io.HttpRequest request, - ) async => - IORelicWebSocket._(await io.WebSocketTransformer.upgrade(request)); + ) async => IORelicWebSocket._(await io.WebSocketTransformer.upgrade(request)); // Create an `IORelicWebSocket` from an existing `dart:io` `WebSocket` // that has been correctly established using either io.WebSocket.connect, or @@ -62,8 +66,9 @@ class IORelicWebSocket implements RelicWebSocket { onError: (final Object e, final StackTrace st) { if (_events.isClosed) return; final wse = switch (e) { - io.WebSocketException(message: final message) => - WebSocketException(message), + io.WebSocketException(message: final message) => WebSocketException( + message, + ), _ => WebSocketException(e.toString()), }; _events.addError(wse, st); @@ -72,7 +77,8 @@ class IORelicWebSocket implements RelicWebSocket { if (_events.isClosed) return; _events ..add( - CloseReceived(_webSocket.closeCode, _webSocket.closeReason ?? '')) + CloseReceived(_webSocket.closeCode, _webSocket.closeReason ?? ''), + ) ..close(); }, ); @@ -146,15 +152,20 @@ class IORelicWebSocket implements RelicWebSocket { /// Throw if the given close code is not valid. void _checkCloseCode(final int? code) { if (code != null && code != 1000 && !(code >= 3000 && code <= 4999)) { - throw ArgumentError('Invalid argument: $code, close code must be 1000 or ' - 'in the range 3000-4999'); + throw ArgumentError( + 'Invalid argument: $code, close code must be 1000 or ' + 'in the range 3000-4999', + ); } } /// Throw if the given close reason is not valid. void _checkCloseReason(final String? reason) { if (reason != null && utf8.encode(reason).length > 123) { - throw ArgumentError.value(reason, 'reason', - 'reason must be <= 123 bytes long when encoded as UTF-8'); + throw ArgumentError.value( + reason, + 'reason', + 'reason must be <= 123 bytes long when encoded as UTF-8', + ); } } diff --git a/lib/src/adapter/io/io_serve.dart b/lib/src/adapter/io/io_serve.dart index ee1360ca..569bfaa7 100644 --- a/lib/src/adapter/io/io_serve.dart +++ b/lib/src/adapter/io/io_serve.dart @@ -21,12 +21,14 @@ extension RelicAppIOServeEx on RelicApp { final int backlog = 0, final bool shared = false, }) { - return run(() => IOAdapter.bind( - address ?? InternetAddress.loopbackIPv4, - port: port, - context: securityContext, - backlog: backlog, - shared: shared, - )); + return run( + () => IOAdapter.bind( + address ?? InternetAddress.loopbackIPv4, + port: port, + context: securityContext, + backlog: backlog, + shared: shared, + ), + ); } } diff --git a/lib/src/adapter/io/response.dart b/lib/src/adapter/io/response.dart index 771e2b65..c29f109b 100644 --- a/lib/src/adapter/io/response.dart +++ b/lib/src/adapter/io/response.dart @@ -9,9 +9,7 @@ extension ResponseExIo on Response { /// /// This method sets the status code, headers, and body on the [httpResponse] /// and returns a [Future] that completes when the body has been written. - Future writeHttpResponse( - final HttpResponse httpResponse, - ) async { + Future writeHttpResponse(final HttpResponse httpResponse) async { // Set the status code. httpResponse.statusCode = statusCode; @@ -20,6 +18,6 @@ extension ResponseExIo on Response { return httpResponse .addStream(body.read()) - .then((final _) => httpResponse.close()); + .then((_) => httpResponse.close()); } } diff --git a/lib/src/body/body.dart b/lib/src/body/body.dart index 36522dba..682a4957 100644 --- a/lib/src/body/body.dart +++ b/lib/src/body/body.dart @@ -109,9 +109,10 @@ class Body { this.contentLength, { final Encoding? encoding, final MimeType? mimeType, - }) : bodyType = mimeType == null - ? null - : BodyType(mimeType: mimeType, encoding: encoding); + }) : bodyType = + mimeType == null + ? null + : BodyType(mimeType: mimeType, encoding: encoding); /// Creates an empty body. /// @@ -172,8 +173,10 @@ class Body { firstNonWhiteSpace++; } - final prefix = content.substring(firstNonWhiteSpace, - min(end, firstNonWhiteSpace + 14)); // 14 max length needed + final prefix = content.substring( + firstNonWhiteSpace, + min(end, firstNonWhiteSpace + 14), + ); // 14 max length needed if (prefix.startsWith('{') || prefix.startsWith('[')) { return MimeType.json; diff --git a/lib/src/body/types/body_type.dart b/lib/src/body/types/body_type.dart index b849ddf5..978b7a56 100644 --- a/lib/src/body/types/body_type.dart +++ b/lib/src/body/types/body_type.dart @@ -34,10 +34,7 @@ class BodyType { /// The encoding of the body. final Encoding? encoding; - const BodyType({ - required this.mimeType, - this.encoding, - }); + const BodyType({required this.mimeType, this.encoding}); /// Returns the value to use for the Content-Type header. /// diff --git a/lib/src/handler/cascade.dart b/lib/src/handler/cascade.dart index dff9ea71..9c364cb8 100644 --- a/lib/src/handler/cascade.dart +++ b/lib/src/handler/cascade.dart @@ -39,15 +39,17 @@ class Cascade { /// considered unacceptable. If [shouldCascade] is passed, responses for which /// it returns `true` are considered unacceptable. [statusCodes] and /// [shouldCascade] may not both be passed. - Cascade( - {final Iterable? statusCodes, - final bool Function(Response)? shouldCascade}) - : _shouldCascade = _computeShouldCascade(statusCodes, shouldCascade), - _parent = null, - _handler = null { + Cascade({ + final Iterable? statusCodes, + final bool Function(Response)? shouldCascade, + }) : _shouldCascade = _computeShouldCascade(statusCodes, shouldCascade), + _parent = null, + _handler = null { if (statusCodes != null && shouldCascade != null) { - throw ArgumentError('statusCodes and shouldCascade may not both be ' - 'passed.'); + throw ArgumentError( + 'statusCodes and shouldCascade may not both be ' + 'passed.', + ); } } @@ -68,8 +70,10 @@ class Cascade { Handler get handler { final handler = _handler; if (handler == null) { - throw StateError("Can't get a handler for a cascade with no inner " - 'handlers.'); + throw StateError( + "Can't get a handler for a cascade with no inner " + 'handlers.', + ); } return (final ctx) async { @@ -86,7 +90,9 @@ class Cascade { /// Computes the [Cascade._shouldCascade] function based on the user's /// parameters. _ShouldCascade _computeShouldCascade( - Iterable? statusCodes, final bool Function(Response)? shouldCascade) { + Iterable? statusCodes, + final bool Function(Response)? shouldCascade, +) { if (shouldCascade != null) return shouldCascade; statusCodes ??= [404, 405]; final statusCodeSet = statusCodes.toSet(); diff --git a/lib/src/handler/handler.dart b/lib/src/handler/handler.dart index 1f04fc04..81ea3aac 100644 --- a/lib/src/handler/handler.dart +++ b/lib/src/handler/handler.dart @@ -65,8 +65,8 @@ typedef Handler = FutureOr Function(NewContext ctx); /// /// It takes a [RespondableContext] and must return a [FutureOr]. /// This is useful for handlers that are guaranteed to generate a response. -typedef ResponseHandler = FutureOr Function( - RespondableContext ctx); +typedef ResponseHandler = + FutureOr Function(RespondableContext ctx); /// A handler specifically designed to produce a [HijackContext]. /// @@ -80,10 +80,8 @@ typedef HijackHandler = FutureOr Function(HijackableContext ctx); /// This typedef is used to define how exceptions should be handled in the /// context of processing requests. It takes in the [error] and [stackTrace] /// and returns a [Response] after processing the exception. -typedef ExceptionHandler = FutureOr Function( - Object error, - StackTrace stackTrace, -); +typedef ExceptionHandler = + FutureOr Function(Object error, StackTrace stackTrace); /// A simplified handler function that takes a [Request] and returns a [Response]. /// diff --git a/lib/src/headers/codecs/common_types_codecs.dart b/lib/src/headers/codecs/common_types_codecs.dart index 7cb33796..f52ad743 100644 --- a/lib/src/headers/codecs/common_types_codecs.dart +++ b/lib/src/headers/codecs/common_types_codecs.dart @@ -86,8 +86,10 @@ int parsePositiveInt(final String value) { Iterable encodePositiveInt(final int i) => i < 0 ? throw ArgumentError() : [i.toString()]; -const positiveIntHeaderCodec = - HeaderCodec.single(parsePositiveInt, encodePositiveInt); +const positiveIntHeaderCodec = HeaderCodec.single( + parsePositiveInt, + encodePositiveInt, +); /// Parses a boolean from the given [value] and returns it as a `bool`. /// @@ -117,8 +119,10 @@ bool parsePositiveBool(final String value) { /// Encode a boolean to a iterable of string, if true. Iterable encodePositiveBool(final bool b) => [if (b) b.toString()]; -const positiveBoolHeaderCodec = - HeaderCodec.single(parsePositiveBool, encodePositiveBool); +const positiveBoolHeaderCodec = HeaderCodec.single( + parsePositiveBool, + encodePositiveBool, +); /// Parses a string from the given [value] and returns it as a `String`. /// diff --git a/lib/src/headers/exception/header_exception.dart b/lib/src/headers/exception/header_exception.dart index 139f76af..b71780b9 100644 --- a/lib/src/headers/exception/header_exception.dart +++ b/lib/src/headers/exception/header_exception.dart @@ -42,8 +42,11 @@ class InvalidHeaderException extends HeaderException { /// Creates an [InvalidHeaderException] with a [description] describing the error /// and the [headerType] indicating the problematic header. - const InvalidHeaderException(super.description, - {required super.headerType, this.raw = const []}); + const InvalidHeaderException( + super.description, { + required super.headerType, + this.raw = const [], + }); @override String get httpResponseBody => "Invalid '$headerType' header: $description"; diff --git a/lib/src/headers/extension/string_list_extensions.dart b/lib/src/headers/extension/string_list_extensions.dart index 170b3363..fa2a1114 100644 --- a/lib/src/headers/extension/string_list_extensions.dart +++ b/lib/src/headers/extension/string_list_extensions.dart @@ -17,17 +17,16 @@ extension StringListExtensions on Iterable { Iterable splitTrimAndFilterUnique({ final String separator = ',', final bool emptyCheck = true, - }) => - LinkedHashSet.from( - splitAndTrim(separator: separator, emptyCheck: emptyCheck)); + }) => LinkedHashSet.from( + splitAndTrim(separator: separator, emptyCheck: emptyCheck), + ); Iterable splitAndTrim({ final String separator = ',', final bool emptyCheck = true, - }) => - expand((final element) => element.split(separator)) - .map((final el) => el.trim()) - .where((final e) => !emptyCheck || e.isNotEmpty); + }) => expand((final element) => element.split(separator)) + .map((final el) => el.trim()) + .where((final e) => !emptyCheck || e.isNotEmpty); } extension StringExtensions on String { @@ -58,8 +57,9 @@ extension StringExtensions on String { /// Checks if the string is a valid email address. bool isValidEmail() { - return RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$') - .hasMatch(this); + return RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ).hasMatch(this); } bool isValidLanguageCode() { diff --git a/lib/src/headers/header_accessor.dart b/lib/src/headers/header_accessor.dart index 938dcdc8..44c8ef0b 100644 --- a/lib/src/headers/header_accessor.dart +++ b/lib/src/headers/header_accessor.dart @@ -222,10 +222,8 @@ extension type const Header(HeaderTuple tuple) { } /// Internal record for bundling an [accessor] with its externalized state [headers]. -typedef HeaderTuple = ({ - HeaderAccessor accessor, - HeadersBase headers, -}); +typedef HeaderTuple = + ({HeaderAccessor accessor, HeadersBase headers}); /// Throws an [InvalidHeaderException] with the appropriate message based on /// the type of the given [exception]. diff --git a/lib/src/headers/headers.dart b/lib/src/headers/headers.dart index b775ce38..70dfcb48 100644 --- a/lib/src/headers/headers.dart +++ b/lib/src/headers/headers.dart @@ -96,12 +96,16 @@ class Headers extends HeadersBase { Headers._empty() : this._fromEntries(const {}); Headers._fromEntries( - final Iterable>> entries) - : this._(CaseInsensitiveMap.from(Map.fromEntries( - entries - .where((final e) => e.value.isNotEmpty) - .map((final e) => MapEntry(e.key, List.unmodifiable(e.value))), - ))); + final Iterable>> entries, + ) : this._( + CaseInsensitiveMap.from( + Map.fromEntries( + entries + .where((final e) => e.value.isNotEmpty) + .map((final e) => MapEntry(e.key, List.unmodifiable(e.value))), + ), + ), + ); Headers._(super.backing) : super._(); @@ -114,17 +118,25 @@ class Headers extends HeadersBase { /// Date-related headers static const date = HeaderAccessor(Headers.dateHeader, dateTimeHeaderCodec); - static const expires = - HeaderAccessor(Headers.expiresHeader, dateTimeHeaderCodec); + static const expires = HeaderAccessor( + Headers.expiresHeader, + dateTimeHeaderCodec, + ); - static const lastModified = - HeaderAccessor(Headers.lastModifiedHeader, dateTimeHeaderCodec); + static const lastModified = HeaderAccessor( + Headers.lastModifiedHeader, + dateTimeHeaderCodec, + ); - static const ifModifiedSince = - HeaderAccessor(Headers.ifModifiedSinceHeader, dateTimeHeaderCodec); + static const ifModifiedSince = HeaderAccessor( + Headers.ifModifiedSinceHeader, + dateTimeHeaderCodec, + ); - static const ifUnmodifiedSince = - HeaderAccessor(Headers.ifUnmodifiedSinceHeader, dateTimeHeaderCodec); + static const ifUnmodifiedSince = HeaderAccessor( + Headers.ifUnmodifiedSinceHeader, + dateTimeHeaderCodec, + ); /// General Headers static const origin = HeaderAccessor(Headers.originHeader, uriHeaderCodec); @@ -138,188 +150,277 @@ class Headers extends HeadersBase { static const host = HeaderAccessor(Headers.hostHeader, HostHeader.codec); - static const acceptEncoding = - HeaderAccessor(Headers.acceptEncodingHeader, AcceptEncodingHeader.codec); + static const acceptEncoding = HeaderAccessor( + Headers.acceptEncodingHeader, + AcceptEncodingHeader.codec, + ); - static const acceptLanguage = - HeaderAccessor(Headers.acceptLanguageHeader, AcceptLanguageHeader.codec); + static const acceptLanguage = HeaderAccessor( + Headers.acceptLanguageHeader, + AcceptLanguageHeader.codec, + ); static const accessControlRequestHeaders = HeaderAccessor( - Headers.accessControlRequestHeadersHeader, stringListCodec); - - static const accessControlRequestMethod = - HeaderAccessor(Headers.accessControlRequestMethodHeader, methodCodec); + Headers.accessControlRequestHeadersHeader, + stringListCodec, + ); - static const age = HeaderAccessor( - Headers.ageHeader, - positiveIntHeaderCodec, + static const accessControlRequestMethod = HeaderAccessor( + Headers.accessControlRequestMethodHeader, + methodCodec, ); - static const authorization = - HeaderAccessor(Headers.authorizationHeader, AuthorizationHeader.codec); + static const age = HeaderAccessor(Headers.ageHeader, positiveIntHeaderCodec); - static const connection = - HeaderAccessor(Headers.connectionHeader, ConnectionHeader.codec); + static const authorization = HeaderAccessor( + Headers.authorizationHeader, + AuthorizationHeader.codec, + ); - static const contentLength = - HeaderAccessor(Headers.contentLengthHeader, intHeaderCodec); + static const connection = HeaderAccessor( + Headers.connectionHeader, + ConnectionHeader.codec, + ); - static const expect = - HeaderAccessor(Headers.expectHeader, ExpectHeader.codec); + static const contentLength = HeaderAccessor( + Headers.contentLengthHeader, + intHeaderCodec, + ); - static const ifMatch = - HeaderAccessor(Headers.ifMatchHeader, IfMatchHeader.codec); + static const expect = HeaderAccessor( + Headers.expectHeader, + ExpectHeader.codec, + ); - static const ifNoneMatch = - HeaderAccessor(Headers.ifNoneMatchHeader, IfNoneMatchHeader.codec); + static const ifMatch = HeaderAccessor( + Headers.ifMatchHeader, + IfMatchHeader.codec, + ); - static const ifRange = - HeaderAccessor(Headers.ifRangeHeader, IfRangeHeader.codec); + static const ifNoneMatch = HeaderAccessor( + Headers.ifNoneMatchHeader, + IfNoneMatchHeader.codec, + ); - static const maxForwards = - HeaderAccessor(Headers.maxForwardsHeader, positiveIntHeaderCodec); + static const ifRange = HeaderAccessor( + Headers.ifRangeHeader, + IfRangeHeader.codec, + ); + + static const maxForwards = HeaderAccessor( + Headers.maxForwardsHeader, + positiveIntHeaderCodec, + ); static const proxyAuthorization = HeaderAccessor( - Headers.proxyAuthorizationHeader, AuthorizationHeader.codec); + Headers.proxyAuthorizationHeader, + AuthorizationHeader.codec, + ); static const range = HeaderAccessor(Headers.rangeHeader, RangeHeader.codec); static const referer = HeaderAccessor(Headers.refererHeader, uriHeaderCodec); - static const userAgent = - HeaderAccessor(Headers.userAgentHeader, stringHeaderCodec); + static const userAgent = HeaderAccessor( + Headers.userAgentHeader, + stringHeaderCodec, + ); static const te = HeaderAccessor(Headers.teHeader, TEHeader.codec); - static const upgrade = - HeaderAccessor(Headers.upgradeHeader, UpgradeHeader.codec); + static const upgrade = HeaderAccessor( + Headers.upgradeHeader, + UpgradeHeader.codec, + ); /// Response Headers - static const location = - HeaderAccessor(Headers.locationHeader, uriHeaderCodec); + static const location = HeaderAccessor( + Headers.locationHeader, + uriHeaderCodec, + ); - static const xPoweredBy = - HeaderAccessor(Headers.xPoweredByHeader, stringHeaderCodec); + static const xPoweredBy = HeaderAccessor( + Headers.xPoweredByHeader, + stringHeaderCodec, + ); static const accessControlAllowOrigin = HeaderAccessor( - Headers.accessControlAllowOriginHeader, - AccessControlAllowOriginHeader.codec); + Headers.accessControlAllowOriginHeader, + AccessControlAllowOriginHeader.codec, + ); static const accessControlExposeHeaders = HeaderAccessor( - Headers.accessControlExposeHeadersHeader, - AccessControlExposeHeadersHeader.codec); + Headers.accessControlExposeHeadersHeader, + AccessControlExposeHeadersHeader.codec, + ); - static const accessControlMaxAge = - HeaderAccessor(Headers.accessControlMaxAgeHeader, intHeaderCodec); + static const accessControlMaxAge = HeaderAccessor( + Headers.accessControlMaxAgeHeader, + intHeaderCodec, + ); static const allow = HeaderAccessor( Headers.allowHeader, HeaderCodec(parseMethodSet, encodeMethodList), ); - static const cacheControl = - HeaderAccessor(Headers.cacheControlHeader, CacheControlHeader.codec); + static const cacheControl = HeaderAccessor( + Headers.cacheControlHeader, + CacheControlHeader.codec, + ); static const contentEncoding = HeaderAccessor( - Headers.contentEncodingHeader, ContentEncodingHeader.codec); + Headers.contentEncodingHeader, + ContentEncodingHeader.codec, + ); static const contentLanguage = HeaderAccessor( - Headers.contentLanguageHeader, ContentLanguageHeader.codec); + Headers.contentLanguageHeader, + ContentLanguageHeader.codec, + ); - static const contentLocation = - HeaderAccessor(Headers.contentLocationHeader, uriHeaderCodec); + static const contentLocation = HeaderAccessor( + Headers.contentLocationHeader, + uriHeaderCodec, + ); - static const contentRange = - HeaderAccessor(Headers.contentRangeHeader, ContentRangeHeader.codec); + static const contentRange = HeaderAccessor( + Headers.contentRangeHeader, + ContentRangeHeader.codec, + ); static const etag = HeaderAccessor(Headers.etagHeader, ETagHeader.codec); static const proxyAuthenticate = HeaderAccessor( - Headers.proxyAuthenticateHeader, AuthenticationHeader.codec); + Headers.proxyAuthenticateHeader, + AuthenticationHeader.codec, + ); - static const retryAfter = - HeaderAccessor(Headers.retryAfterHeader, RetryAfterHeader.codec); + static const retryAfter = HeaderAccessor( + Headers.retryAfterHeader, + RetryAfterHeader.codec, + ); static const trailer = HeaderAccessor(Headers.trailerHeader, stringListCodec); static const vary = HeaderAccessor(Headers.varyHeader, VaryHeader.codec); - static const wwwAuthenticate = - HeaderAccessor(Headers.wwwAuthenticateHeader, AuthenticationHeader.codec); + static const wwwAuthenticate = HeaderAccessor( + Headers.wwwAuthenticateHeader, + AuthenticationHeader.codec, + ); static const contentDisposition = HeaderAccessor( - Headers.contentDispositionHeader, ContentDispositionHeader.codec); + Headers.contentDispositionHeader, + ContentDispositionHeader.codec, + ); /// Common Headers (Used in Both Requests and Responses) - static const accept = - HeaderAccessor(Headers.acceptHeader, AcceptHeader.codec); + static const accept = HeaderAccessor( + Headers.acceptHeader, + AcceptHeader.codec, + ); - static const acceptRanges = - HeaderAccessor(Headers.acceptRangesHeader, AcceptRangesHeader.codec); + static const acceptRanges = HeaderAccessor( + Headers.acceptRangesHeader, + AcceptRangesHeader.codec, + ); static const transferEncoding = HeaderAccessor( - Headers.transferEncodingHeader, TransferEncodingHeader.codec); + Headers.transferEncodingHeader, + TransferEncodingHeader.codec, + ); - static const cookie = - HeaderAccessor(Headers.cookieHeader, CookieHeader.codec); + static const cookie = HeaderAccessor( + Headers.cookieHeader, + CookieHeader.codec, + ); - static const setCookie = - HeaderAccessor(Headers.setCookieHeader, SetCookieHeader.codec); + static const setCookie = HeaderAccessor( + Headers.setCookieHeader, + SetCookieHeader.codec, + ); /// Security and Modern Headers static const strictTransportSecurity = HeaderAccessor( - Headers.strictTransportSecurityHeader, - StrictTransportSecurityHeader.codec); + Headers.strictTransportSecurityHeader, + StrictTransportSecurityHeader.codec, + ); static const contentSecurityPolicy = HeaderAccessor( - Headers.contentSecurityPolicyHeader, ContentSecurityPolicyHeader.codec); + Headers.contentSecurityPolicyHeader, + ContentSecurityPolicyHeader.codec, + ); - static const referrerPolicy = - HeaderAccessor(Headers.referrerPolicyHeader, ReferrerPolicyHeader.codec); + static const referrerPolicy = HeaderAccessor( + Headers.referrerPolicyHeader, + ReferrerPolicyHeader.codec, + ); static const permissionsPolicy = HeaderAccessor( - Headers.permissionsPolicyHeader, PermissionsPolicyHeader.codec); + Headers.permissionsPolicyHeader, + PermissionsPolicyHeader.codec, + ); static const accessControlAllowCredentials = HeaderAccessor( - Headers.accessControlAllowCredentialsHeader, positiveBoolHeaderCodec); + Headers.accessControlAllowCredentialsHeader, + positiveBoolHeaderCodec, + ); static const accessControlAllowMethods = HeaderAccessor( - Headers.accessControlAllowMethodsHeader, - AccessControlAllowMethodsHeader.codec); + Headers.accessControlAllowMethodsHeader, + AccessControlAllowMethodsHeader.codec, + ); static const accessControlAllowHeaders = HeaderAccessor( - Headers.accessControlAllowHeadersHeader, - AccessControlAllowHeadersHeader.codec); + Headers.accessControlAllowHeadersHeader, + AccessControlAllowHeadersHeader.codec, + ); - static const clearSiteData = - HeaderAccessor(Headers.clearSiteDataHeader, ClearSiteDataHeader.codec); + static const clearSiteData = HeaderAccessor( + Headers.clearSiteDataHeader, + ClearSiteDataHeader.codec, + ); - static const secFetchDest = - HeaderAccessor(Headers.secFetchDestHeader, SecFetchDestHeader.codec); + static const secFetchDest = HeaderAccessor( + Headers.secFetchDestHeader, + SecFetchDestHeader.codec, + ); - static const secFetchMode = - HeaderAccessor(Headers.secFetchModeHeader, SecFetchModeHeader.codec); + static const secFetchMode = HeaderAccessor( + Headers.secFetchModeHeader, + SecFetchModeHeader.codec, + ); - static const secFetchSite = - HeaderAccessor(Headers.secFetchSiteHeader, SecFetchSiteHeader.codec); + static const secFetchSite = HeaderAccessor( + Headers.secFetchSiteHeader, + SecFetchSiteHeader.codec, + ); - static const forwarded = - HeaderAccessor(Headers.forwardedHeader, ForwardedHeader.codec); + static const forwarded = HeaderAccessor( + Headers.forwardedHeader, + ForwardedHeader.codec, + ); - static const xForwardedFor = - HeaderAccessor(Headers.xForwardedForHeader, XForwardedForHeader.codec); + static const xForwardedFor = HeaderAccessor( + Headers.xForwardedForHeader, + XForwardedForHeader.codec, + ); static const crossOriginResourcePolicy = HeaderAccessor( - Headers.crossOriginResourcePolicyHeader, - CrossOriginResourcePolicyHeader.codec); + Headers.crossOriginResourcePolicyHeader, + CrossOriginResourcePolicyHeader.codec, + ); static const crossOriginEmbedderPolicy = HeaderAccessor( - Headers.crossOriginEmbedderPolicyHeader, - CrossOriginEmbedderPolicyHeader.codec); + Headers.crossOriginEmbedderPolicyHeader, + CrossOriginEmbedderPolicyHeader.codec, + ); static const crossOriginOpenerPolicy = HeaderAccessor( - Headers.crossOriginOpenerPolicyHeader, - CrossOriginOpenerPolicyHeader.codec); + Headers.crossOriginOpenerPolicyHeader, + CrossOriginOpenerPolicyHeader.codec, + ); static const _common = { cacheControl, diff --git a/lib/src/headers/mutable_headers.dart b/lib/src/headers/mutable_headers.dart index f72a2a64..f97f6bd6 100644 --- a/lib/src/headers/mutable_headers.dart +++ b/lib/src/headers/mutable_headers.dart @@ -7,7 +7,7 @@ class MutableHeaders extends HeadersBase MutableHeaders() : this._(_BackingStore()); MutableHeaders._from(final Headers headers) - : this._(_BackingStore.from(headers._backing)); + : this._(_BackingStore.from(headers._backing)); Headers _freeze() { // TODO: diff --git a/lib/src/headers/standard_headers_extensions.dart b/lib/src/headers/standard_headers_extensions.dart index 55b3a984..2927672b 100644 --- a/lib/src/headers/standard_headers_extensions.dart +++ b/lib/src/headers/standard_headers_extensions.dart @@ -138,12 +138,13 @@ extension MutableHeadersEx on MutableHeaders { set accessControlAllowOrigin(final AccessControlAllowOriginHeader? value) => Headers.accessControlAllowOrigin[this].set(value); set accessControlExposeHeaders( - final AccessControlExposeHeadersHeader? value) => - Headers.accessControlExposeHeaders[this].set(value); + final AccessControlExposeHeadersHeader? value, + ) => Headers.accessControlExposeHeaders[this].set(value); set accessControlMaxAge(final int? value) => Headers.accessControlMaxAge[this].set(value); - set allow(final Set? value) => Headers.allow[this] - .set(value != null ? SplayTreeSet.of(value, Enum.compareByIndex) : null); + set allow(final Set? value) => Headers.allow[this].set( + value != null ? SplayTreeSet.of(value, Enum.compareByIndex) : null, + ); set cacheControl(final CacheControlHeader? value) => Headers.cacheControl[this].set(value); set contentEncoding(final ContentEncodingHeader? value) => diff --git a/lib/src/headers/typed/headers/accept_encoding_header.dart b/lib/src/headers/typed/headers/accept_encoding_header.dart index 2d77371a..e25d1dd2 100644 --- a/lib/src/headers/typed/headers/accept_encoding_header.dart +++ b/lib/src/headers/typed/headers/accept_encoding_header.dart @@ -8,9 +8,9 @@ final class AcceptEncodingHeader extends WildcardListHeader { static const codec = HeaderCodec(_parse, _encode); /// Constructs an instance with the given encodings - AcceptEncodingHeader.encodings( - {required final List encodings}) - : super(encodings); + AcceptEncodingHeader.encodings({ + required final List encodings, + }) : super(encodings); /// Constructs an instance with a wildcard encoding const AcceptEncodingHeader.wildcard() : super.wildcard(); @@ -52,7 +52,7 @@ class EncodingQuality { /// Constructs an instance of [EncodingQuality]. EncodingQuality(this.encoding, [final double? quality]) - : quality = quality ?? 1.0; + : quality = quality ?? 1.0; /// Parses a string value and returns an [EncodingQuality] instance. factory EncodingQuality.parse(final String value) { diff --git a/lib/src/headers/typed/headers/accept_header.dart b/lib/src/headers/typed/headers/accept_header.dart index 79944651..fac94067 100644 --- a/lib/src/headers/typed/headers/accept_header.dart +++ b/lib/src/headers/typed/headers/accept_header.dart @@ -15,8 +15,8 @@ final class AcceptHeader { /// Constructs an [AcceptHeader] instance with the specified media ranges. AcceptHeader({required final List mediaRanges}) - : assert(mediaRanges.isNotEmpty), - mediaRanges = List.unmodifiable(mediaRanges); + : assert(mediaRanges.isNotEmpty), + mediaRanges = List.unmodifiable(mediaRanges); /// Parses the Accept header value and returns an [AcceptHeader] instance. /// @@ -40,8 +40,10 @@ final class AcceptHeader { bool operator ==(final Object other) => identical(this, other) || other is AcceptHeader && - const ListEquality() - .equals(mediaRanges, other.mediaRanges); + const ListEquality().equals( + mediaRanges, + other.mediaRanges, + ); @override int get hashCode => const ListEquality().hash(mediaRanges); @@ -64,7 +66,7 @@ class MediaRange { /// Constructs a [MediaRange] instance with the specified type, subtype, /// quality, and parameters. MediaRange(this.type, this.subtype, {final double? quality}) - : quality = quality ?? 1.0; + : quality = quality ?? 1.0; /// Parses a media range string and returns a [MediaRange] instance. /// @@ -93,11 +95,7 @@ class MediaRange { } } - return MediaRange( - type, - subtype, - quality: quality, - ); + return MediaRange(type, subtype, quality: quality); } /// Converts the [MediaRange] instance into a string representation suitable for HTTP headers. diff --git a/lib/src/headers/typed/headers/accept_language_header.dart b/lib/src/headers/typed/headers/accept_language_header.dart index 551cf406..42cc7c82 100644 --- a/lib/src/headers/typed/headers/accept_language_header.dart +++ b/lib/src/headers/typed/headers/accept_language_header.dart @@ -8,9 +8,9 @@ final class AcceptLanguageHeader extends WildcardListHeader { static const codec = HeaderCodec(_parse, _encode); /// Constructs an instance with the given languages - AcceptLanguageHeader.languages( - {required final List languages}) - : super(languages); + AcceptLanguageHeader.languages({ + required final List languages, + }) : super(languages); /// Constructs an instance with a wildcard language const AcceptLanguageHeader.wildcard() : super.wildcard(); @@ -52,7 +52,7 @@ class LanguageQuality { /// Constructs an instance of [LanguageQuality]. const LanguageQuality(this.language, [final double? quality]) - : quality = quality ?? 1.0; + : quality = quality ?? 1.0; /// Parses a string value and returns a [LanguageQuality] instance. factory LanguageQuality.parse(final String value) { diff --git a/lib/src/headers/typed/headers/accept_ranges_header.dart b/lib/src/headers/typed/headers/accept_ranges_header.dart index 26fe7b65..c8c03492 100644 --- a/lib/src/headers/typed/headers/accept_ranges_header.dart +++ b/lib/src/headers/typed/headers/accept_ranges_header.dart @@ -5,8 +5,9 @@ import '../../../../relic.dart'; /// This class manages the range units that the server supports. final class AcceptRangesHeader { static const codec = HeaderCodec.single(AcceptRangesHeader.parse, __encode); - static List __encode(final AcceptRangesHeader value) => - [value._encode()]; + static List __encode(final AcceptRangesHeader value) => [ + value._encode(), + ]; /// The range unit supported by the server, or `none` if not supported. final String rangeUnit; diff --git a/lib/src/headers/typed/headers/access_control_allow_headers_header.dart b/lib/src/headers/typed/headers/access_control_allow_headers_header.dart index e4143fb0..9aa246e1 100644 --- a/lib/src/headers/typed/headers/access_control_allow_headers_header.dart +++ b/lib/src/headers/typed/headers/access_control_allow_headers_header.dart @@ -9,9 +9,9 @@ final class AccessControlAllowHeadersHeader extends WildcardListHeader { static const codec = HeaderCodec(_parse, _encode); /// Constructs an instance allowing specific headers to be allowed. - AccessControlAllowHeadersHeader.headers( - {required final Iterable headers}) - : super(List.from(headers)); + AccessControlAllowHeadersHeader.headers({ + required final Iterable headers, + }) : super(List.from(headers)); /// Constructs an instance allowing all headers to be allowed (`*`). const AccessControlAllowHeadersHeader.wildcard() : super.wildcard(); @@ -26,8 +26,10 @@ final class AccessControlAllowHeadersHeader extends WildcardListHeader { List get headers => values; static AccessControlAllowHeadersHeader _parse(final Iterable values) { - final parsed = - WildcardListHeader.parse(values, (final String value) => value); + final parsed = WildcardListHeader.parse( + values, + (final String value) => value, + ); if (parsed.isWildcard) { return const AccessControlAllowHeadersHeader.wildcard(); @@ -37,7 +39,8 @@ final class AccessControlAllowHeadersHeader extends WildcardListHeader { } static List encodeHeader( - final AccessControlAllowHeadersHeader header) { + final AccessControlAllowHeadersHeader header, + ) { return header.encode((final String str) => str).toList(); } diff --git a/lib/src/headers/typed/headers/access_control_allow_methods_header.dart b/lib/src/headers/typed/headers/access_control_allow_methods_header.dart index 190bdf09..9f4655d8 100644 --- a/lib/src/headers/typed/headers/access_control_allow_methods_header.dart +++ b/lib/src/headers/typed/headers/access_control_allow_methods_header.dart @@ -10,7 +10,7 @@ final class AccessControlAllowMethodsHeader extends WildcardListHeader { /// Constructs an instance allowing specific methods to be allowed. AccessControlAllowMethodsHeader.methods({required final List methods}) - : super(methods); + : super(methods); /// Constructs an instance allowing all methods to be allowed (`*`). const AccessControlAllowMethodsHeader.wildcard() : super.wildcard(); @@ -35,7 +35,8 @@ final class AccessControlAllowMethodsHeader extends WildcardListHeader { } static List encodeHeader( - final AccessControlAllowMethodsHeader header) { + final AccessControlAllowMethodsHeader header, + ) { return header.encode((final Method m) => m.value).toList(); } diff --git a/lib/src/headers/typed/headers/access_control_allow_origin_header.dart b/lib/src/headers/typed/headers/access_control_allow_origin_header.dart index 702c1a84..20de333e 100644 --- a/lib/src/headers/typed/headers/access_control_allow_origin_header.dart +++ b/lib/src/headers/typed/headers/access_control_allow_origin_header.dart @@ -5,10 +5,13 @@ import '../../../../relic.dart'; /// This header specifies which origins are allowed to access the resource. /// It can be a specific origin or a wildcard (`*`) to allow any origin. final class AccessControlAllowOriginHeader { - static const codec = - HeaderCodec.single(AccessControlAllowOriginHeader.parse, __encode); - static List __encode(final AccessControlAllowOriginHeader value) => - [value._encode()]; + static const codec = HeaderCodec.single( + AccessControlAllowOriginHeader.parse, + __encode, + ); + static List __encode(final AccessControlAllowOriginHeader value) => [ + value._encode(), + ]; /// The allowed origin URI, if specified. final Uri? origin; @@ -18,12 +21,12 @@ final class AccessControlAllowOriginHeader { /// Constructs an instance allowing a specific origin. const AccessControlAllowOriginHeader.origin({required this.origin}) - : isWildcard = false; + : isWildcard = false; /// Constructs an instance allowing any origin (`*`). const AccessControlAllowOriginHeader.wildcard() - : origin = null, - isWildcard = true; + : origin = null, + isWildcard = true; /// Parses the Access-Control-Allow-Origin header value and /// returns an [AccessControlAllowOriginHeader] instance. @@ -40,9 +43,7 @@ final class AccessControlAllowOriginHeader { } try { - return AccessControlAllowOriginHeader.origin( - origin: Uri.parse(trimmed), - ); + return AccessControlAllowOriginHeader.origin(origin: Uri.parse(trimmed)); } catch (_) { throw const FormatException('Invalid URI format'); } diff --git a/lib/src/headers/typed/headers/access_control_expose_headers_header.dart b/lib/src/headers/typed/headers/access_control_expose_headers_header.dart index 9ffb1281..68932e99 100644 --- a/lib/src/headers/typed/headers/access_control_expose_headers_header.dart +++ b/lib/src/headers/typed/headers/access_control_expose_headers_header.dart @@ -10,9 +10,9 @@ final class AccessControlExposeHeadersHeader static const codec = HeaderCodec(_parse, _encode); /// Constructs an instance allowing specific headers to be exposed. - AccessControlExposeHeadersHeader.headers( - {required final Iterable headers}) - : super(List.from(headers)); + AccessControlExposeHeadersHeader.headers({ + required final Iterable headers, + }) : super(List.from(headers)); /// Constructs an instance allowing all headers to be exposed (`*`). const AccessControlExposeHeadersHeader.wildcard() : super.wildcard(); @@ -20,7 +20,8 @@ final class AccessControlExposeHeadersHeader /// Parses the Access-Control-Expose-Headers header value and returns an /// [AccessControlExposeHeadersHeader] instance. factory AccessControlExposeHeadersHeader.parse( - final Iterable values) { + final Iterable values, + ) { return _parse(values); } @@ -28,9 +29,12 @@ final class AccessControlExposeHeadersHeader List get headers => values; static AccessControlExposeHeadersHeader _parse( - final Iterable values) { - final parsed = - WildcardListHeader.parse(values, (final String value) => value); + final Iterable values, + ) { + final parsed = WildcardListHeader.parse( + values, + (final String value) => value, + ); if (parsed.isWildcard) { return const AccessControlExposeHeadersHeader.wildcard(); @@ -40,7 +44,8 @@ final class AccessControlExposeHeadersHeader } static List encodeHeader( - final AccessControlExposeHeadersHeader header) { + final AccessControlExposeHeadersHeader header, + ) { return header.encode((final String str) => str).toList(); } diff --git a/lib/src/headers/typed/headers/authentication_header.dart b/lib/src/headers/typed/headers/authentication_header.dart index c11c1c96..2a7496a7 100644 --- a/lib/src/headers/typed/headers/authentication_header.dart +++ b/lib/src/headers/typed/headers/authentication_header.dart @@ -5,8 +5,9 @@ import '../../../../relic.dart'; /// A class representing the HTTP Authentication header. final class AuthenticationHeader { static const codec = HeaderCodec.single(AuthenticationHeader.parse, __encode); - static List __encode(final AuthenticationHeader value) => - [value._encode()]; + static List __encode(final AuthenticationHeader value) => [ + value._encode(), + ]; /// The authentication scheme (e.g., "Basic", "Bearer", "Digest"). final String scheme; @@ -66,10 +67,12 @@ final class AuthenticationHeader { /// Converts the [AuthenticationHeader] instance into a string representation /// suitable for HTTP headers. String _encode() { - final paramsString = parameters.map((final param) { - if (param.key.isEmpty) return param.value; - return '${param.key}="${param.value}"'; - }).join(', '); + final paramsString = parameters + .map((final param) { + if (param.key.isEmpty) return param.value; + return '${param.key}="${param.value}"'; + }) + .join(', '); return '$scheme $paramsString'; } @@ -78,12 +81,16 @@ final class AuthenticationHeader { identical(this, other) || other is AuthenticationHeader && scheme == other.scheme && - const ListEquality() - .equals(parameters, other.parameters); + const ListEquality().equals( + parameters, + other.parameters, + ); @override int get hashCode => Object.hash( - scheme, const ListEquality().hash(parameters)); + scheme, + const ListEquality().hash(parameters), + ); @override String toString() { diff --git a/lib/src/headers/typed/headers/authorization_header.dart b/lib/src/headers/typed/headers/authorization_header.dart index 8de1ddc3..144fced0 100644 --- a/lib/src/headers/typed/headers/authorization_header.dart +++ b/lib/src/headers/typed/headers/authorization_header.dart @@ -9,8 +9,9 @@ import '../../../../relic.dart'; /// The concrete subclasses handle specific header formats. abstract class AuthorizationHeader { static const codec = HeaderCodec.single(AuthorizationHeader.parse, __encode); - static List __encode(final AuthorizationHeader value) => - [value._encode()]; + static List __encode(final AuthorizationHeader value) => [ + value._encode(), + ]; /// Returns the value of the Authorization header as a string. String get headerValue; @@ -58,9 +59,7 @@ final class BearerAuthorizationHeader extends AuthorizationHeader { /// Constructs a [BearerAuthorizationHeader] with the specified token. /// /// The token must not be empty. - BearerAuthorizationHeader({ - required this.token, - }) { + BearerAuthorizationHeader({required this.token}) { if (token.isEmpty) { throw const FormatException('Bearer token cannot be empty.'); } @@ -84,9 +83,7 @@ final class BearerAuthorizationHeader extends AuthorizationHeader { throw const FormatException('Bearer token cannot be empty.'); } - return BearerAuthorizationHeader( - token: token, - ); + return BearerAuthorizationHeader(token: token); } /// Returns the full authorization string, including the "Bearer " prefix. @@ -144,10 +141,7 @@ final class BasicAuthorizationHeader extends AuthorizationHeader { /// /// The credentials are encoded as "username:password" in Base64 and prefixed /// with "Basic ". - BasicAuthorizationHeader({ - required this.username, - required this.password, - }) { + BasicAuthorizationHeader({required this.username, required this.password}) { if (username.isEmpty) { throw const FormatException('Username cannot be empty'); } @@ -368,7 +362,7 @@ final class DigestAuthorizationHeader extends AuthorizationHeader { if (qop != null) '$_qop="$qop"', if (nc != null) '$_nc="$nc"', if (cnonce != null) '$_cnonce="$cnonce"', - if (opaque != null) '$_opaque="$opaque"' + if (opaque != null) '$_opaque="$opaque"', ].join(', '); } @@ -389,17 +383,17 @@ final class DigestAuthorizationHeader extends AuthorizationHeader { @override int get hashCode => Object.hashAll([ - username, - realm, - nonce, - uri, - response, - algorithm, - qop, - nc, - cnonce, - opaque, - ]); + username, + realm, + nonce, + uri, + response, + algorithm, + qop, + nc, + cnonce, + opaque, + ]); @override String toString() { diff --git a/lib/src/headers/typed/headers/cache_control_header.dart b/lib/src/headers/typed/headers/cache_control_header.dart index b60c1154..190628e8 100644 --- a/lib/src/headers/typed/headers/cache_control_header.dart +++ b/lib/src/headers/typed/headers/cache_control_header.dart @@ -8,8 +8,9 @@ import '../../extension/string_list_extensions.dart'; /// the appropriate header string. final class CacheControlHeader { static const codec = HeaderCodec(CacheControlHeader.parse, __encode); - static List __encode(final CacheControlHeader value) => - [value._encode()]; + static List __encode(final CacheControlHeader value) => [ + value._encode(), + ]; // Cache-Control directive constants static const String _noCacheDirective = 'no-cache'; static const String _noStoreDirective = 'no-store'; @@ -144,9 +145,10 @@ final class CacheControlHeader { // Check for invalid directives final invalidDirectives = directives.where( - (final directive) => !_validDirectives.any( - (final validDirective) => directive.startsWith(validDirective), - ), + (final directive) => + !_validDirectives.any( + (final validDirective) => directive.startsWith(validDirective), + ), ); if (!foundOneDirective || invalidDirectives.isNotEmpty) { @@ -175,19 +177,24 @@ final class CacheControlHeader { maxAge = int.tryParse(directive.substring(_maxAgeDirective.length + 1)); } else if (directive.startsWith('$_staleWhileRevalidateDirective=')) { staleWhileRevalidate = int.tryParse( - directive.substring(_staleWhileRevalidateDirective.length + 1)); + directive.substring(_staleWhileRevalidateDirective.length + 1), + ); } else if (directive.startsWith('$_sMaxAgeDirective=')) { - sMaxAge = - int.tryParse(directive.substring(_sMaxAgeDirective.length + 1)); + sMaxAge = int.tryParse( + directive.substring(_sMaxAgeDirective.length + 1), + ); } else if (directive.startsWith('$_staleIfErrorDirective=')) { staleIfError = int.tryParse( - directive.substring(_staleIfErrorDirective.length + 1)); + directive.substring(_staleIfErrorDirective.length + 1), + ); } else if (directive.startsWith('$_maxStaleDirective=')) { - maxStale = - int.tryParse(directive.substring(_maxStaleDirective.length + 1)); + maxStale = int.tryParse( + directive.substring(_maxStaleDirective.length + 1), + ); } else if (directive.startsWith('$_minFreshDirective=')) { - minFresh = - int.tryParse(directive.substring(_minFreshDirective.length + 1)); + minFresh = int.tryParse( + directive.substring(_minFreshDirective.length + 1), + ); } } @@ -273,23 +280,23 @@ final class CacheControlHeader { @override int get hashCode => Object.hashAll([ - noCache, - noStore, - maxAge, - staleWhileRevalidate, - publicCache, - privateCache, - mustRevalidate, - proxyRevalidate, - sMaxAge, - noTransform, - onlyIfCached, - staleIfError, - maxStale, - minFresh, - immutable, - mustUnderstand, - ]); + noCache, + noStore, + maxAge, + staleWhileRevalidate, + publicCache, + privateCache, + mustRevalidate, + proxyRevalidate, + sMaxAge, + noTransform, + onlyIfCached, + staleIfError, + maxStale, + minFresh, + immutable, + mustUnderstand, + ]); @override String toString() { diff --git a/lib/src/headers/typed/headers/clear_site_data_header.dart b/lib/src/headers/typed/headers/clear_site_data_header.dart index 965cd194..e474d5e2 100644 --- a/lib/src/headers/typed/headers/clear_site_data_header.dart +++ b/lib/src/headers/typed/headers/clear_site_data_header.dart @@ -8,9 +8,9 @@ final class ClearSiteDataHeader extends WildcardListHeader { static const codec = HeaderCodec(_parse, _encode); /// Constructs an instance allowing specific data types to be cleared. - ClearSiteDataHeader.dataTypes( - {required final List dataTypes}) - : super(dataTypes); + ClearSiteDataHeader.dataTypes({ + required final List dataTypes, + }) : super(dataTypes); /// Constructs an instance allowing all data types to be cleared (`*`). const ClearSiteDataHeader.wildcard() : super.wildcard(); @@ -25,12 +25,13 @@ final class ClearSiteDataHeader extends WildcardListHeader { static ClearSiteDataHeader _parse(final Iterable values) { // Custom parsing logic for ClearSiteData with quote removal - final splitValues = values - .expand((final value) => value.split(',')) - .map((final s) => s.trim().replaceAll('"', '')) - .where((final s) => s.isNotEmpty) - .toSet() - .toList(); + final splitValues = + values + .expand((final value) => value.split(',')) + .map((final s) => s.trim().replaceAll('"', '')) + .where((final s) => s.isNotEmpty) + .toSet() + .toList(); if (splitValues.isEmpty) { throw const FormatException('Value cannot be empty'); @@ -42,7 +43,8 @@ final class ClearSiteDataHeader extends WildcardListHeader { if (splitValues.length > 1 && splitValues.contains('*')) { throw const FormatException( - 'Wildcard (*) cannot be used with other values'); + 'Wildcard (*) cannot be used with other values', + ); } final dataTypes = splitValues.map(ClearSiteDataType.parse).toList(); @@ -56,7 +58,7 @@ final class ClearSiteDataHeader extends WildcardListHeader { return [ header.dataTypes .map((final dataType) => '"${dataType.value}"') - .join(', ') + .join(', '), ]; } } diff --git a/lib/src/headers/typed/headers/connection_header.dart b/lib/src/headers/typed/headers/connection_header.dart index 84efe9a3..47c62039 100644 --- a/lib/src/headers/typed/headers/connection_header.dart +++ b/lib/src/headers/typed/headers/connection_header.dart @@ -10,16 +10,15 @@ import '../../extension/string_list_extensions.dart'; /// connection header values. final class ConnectionHeader { static const codec = HeaderCodec(ConnectionHeader.parse, __encode); - static List __encode(final ConnectionHeader value) => - [value._encode()]; + static List __encode(final ConnectionHeader value) => [ + value._encode(), + ]; /// A list of connection directives (e.g., `keep-alive`, `close`, `upgrade`). final List directives; /// Constructs a [ConnectionHeader] instance with the specified connection directives. - const ConnectionHeader({ - required this.directives, - }); + const ConnectionHeader({required this.directives}); /// Parses the Connection header value and returns a [ConnectionHeader] instance. /// @@ -59,8 +58,10 @@ final class ConnectionHeader { bool operator ==(final Object other) => identical(this, other) || other is ConnectionHeader && - const ListEquality() - .equals(directives, other.directives); + const ListEquality().equals( + directives, + other.directives, + ); @override int get hashCode => diff --git a/lib/src/headers/typed/headers/content_disposition_header.dart b/lib/src/headers/typed/headers/content_disposition_header.dart index bcf78be9..6b22f05e 100644 --- a/lib/src/headers/typed/headers/content_disposition_header.dart +++ b/lib/src/headers/typed/headers/content_disposition_header.dart @@ -10,10 +10,13 @@ import '../../extension/string_list_extensions.dart'; /// `filename*`. It provides functionality to parse the header value and /// construct the appropriate header string. final class ContentDispositionHeader { - static const codec = - HeaderCodec.single(ContentDispositionHeader.parse, __encode); - static List __encode(final ContentDispositionHeader value) => - [value._encode()]; + static const codec = HeaderCodec.single( + ContentDispositionHeader.parse, + __encode, + ); + static List __encode(final ContentDispositionHeader value) => [ + value._encode(), + ]; /// The disposition type, usually "inline", "attachment", or "form-data". final String type; @@ -48,10 +51,7 @@ final class ContentDispositionHeader { final parameters = splitValues.skip(1).map(ContentDispositionParameter.parse).toList(); - return ContentDispositionHeader( - type: type, - parameters: parameters, - ); + return ContentDispositionHeader(type: type, parameters: parameters); } /// Converts the [ContentDispositionHeader] instance into a string @@ -67,12 +67,16 @@ final class ContentDispositionHeader { identical(this, other) || other is ContentDispositionHeader && type == other.type && - const ListEquality() - .equals(parameters, other.parameters); + const ListEquality().equals( + parameters, + other.parameters, + ); @override int get hashCode => Object.hash( - type, const ListEquality().hash(parameters)); + type, + const ListEquality().hash(parameters), + ); @override String toString() { diff --git a/lib/src/headers/typed/headers/content_encoding_header.dart b/lib/src/headers/typed/headers/content_encoding_header.dart index 93568ffc..1b0b19ef 100644 --- a/lib/src/headers/typed/headers/content_encoding_header.dart +++ b/lib/src/headers/typed/headers/content_encoding_header.dart @@ -10,8 +10,9 @@ import '../../extension/string_list_extensions.dart'; /// content encoding header values. final class ContentEncodingHeader { static const codec = HeaderCodec(ContentEncodingHeader.parse, __encode); - static List __encode(final ContentEncodingHeader value) => - [value._encode()]; + static List __encode(final ContentEncodingHeader value) => [ + value._encode(), + ]; /// A list of content encodings. final List encodings; @@ -19,8 +20,8 @@ final class ContentEncodingHeader { /// Constructs a [ContentEncodingHeader] instance with the specified content /// encodings. ContentEncodingHeader({required final List encodings}) - : assert(encodings.isNotEmpty), - encodings = List.unmodifiable(encodings); + : assert(encodings.isNotEmpty), + encodings = List.unmodifiable(encodings); /// Parses the Content-Encoding header value and returns a /// [ContentEncodingHeader] instance. @@ -51,8 +52,10 @@ final class ContentEncodingHeader { bool operator ==(final Object other) => identical(this, other) || other is ContentEncodingHeader && - const ListEquality() - .equals(encodings, other.encodings); + const ListEquality().equals( + encodings, + other.encodings, + ); @override int get hashCode => const ListEquality().hash(encodings); diff --git a/lib/src/headers/typed/headers/content_language_header.dart b/lib/src/headers/typed/headers/content_language_header.dart index e844b58b..5d3d37aa 100644 --- a/lib/src/headers/typed/headers/content_language_header.dart +++ b/lib/src/headers/typed/headers/content_language_header.dart @@ -8,16 +8,17 @@ import '../../extension/string_list_extensions.dart'; /// This class manages the language codes specified in the Content-Language header. final class ContentLanguageHeader { static const codec = HeaderCodec(ContentLanguageHeader.parse, __encode); - static List __encode(final ContentLanguageHeader value) => - [value._encode()]; + static List __encode(final ContentLanguageHeader value) => [ + value._encode(), + ]; /// The list of language codes specified in the header. final List languages; /// Constructs a [ContentLanguageHeader] instance with the specified language codes. ContentLanguageHeader({required final Iterable languages}) - : assert(languages.isNotEmpty), - languages = List.unmodifiable(languages); + : assert(languages.isNotEmpty), + languages = List.unmodifiable(languages); /// Parses the Content-Language header value and returns a [ContentLanguageHeader] instance. /// @@ -28,12 +29,13 @@ final class ContentLanguageHeader { throw const FormatException('Value cannot be empty'); } - final languages = splitValues.map((final language) { - if (!language.isValidLanguageCode()) { - throw const FormatException('Invalid language code'); - } - return language; - }).toList(); + final languages = + splitValues.map((final language) { + if (!language.isValidLanguageCode()) { + throw const FormatException('Invalid language code'); + } + return language; + }).toList(); return ContentLanguageHeader(languages: languages); } diff --git a/lib/src/headers/typed/headers/content_range_header.dart b/lib/src/headers/typed/headers/content_range_header.dart index 8a43f6e4..5de7a904 100644 --- a/lib/src/headers/typed/headers/content_range_header.dart +++ b/lib/src/headers/typed/headers/content_range_header.dart @@ -6,8 +6,9 @@ import '../../../../relic.dart'; /// including cases for unsatisfiable range requests. final class ContentRangeHeader { static const codec = HeaderCodec.single(ContentRangeHeader.parse, __encode); - static List __encode(final ContentRangeHeader value) => - [value._encode()]; + static List __encode(final ContentRangeHeader value) => [ + value._encode(), + ]; /// The unit of the range, e.g. "bytes". final String unit; @@ -22,12 +23,7 @@ final class ContentRangeHeader { final int? size; /// Constructs a [ContentRangeHeader] with the specified range and optional total size. - ContentRangeHeader({ - this.unit = 'bytes', - this.start, - this.end, - this.size, - }) { + ContentRangeHeader({this.unit = 'bytes', this.start, this.end, this.size}) { if (start != null && end != null && start! > end!) { throw const FormatException('Invalid range'); } @@ -58,12 +54,7 @@ final class ContentRangeHeader { // If totalSize is '*', it means the total size is unknown final size = sizeGroup == '*' ? null : int.parse(sizeGroup); - return ContentRangeHeader( - unit: unit, - start: start, - end: end, - size: size, - ); + return ContentRangeHeader(unit: unit, start: start, end: end, size: size); } /// Returns the full content range string in the format "bytes start-end/totalSize". diff --git a/lib/src/headers/typed/headers/content_security_policy_header.dart b/lib/src/headers/typed/headers/content_security_policy_header.dart index 080c197d..ffedc76a 100644 --- a/lib/src/headers/typed/headers/content_security_policy_header.dart +++ b/lib/src/headers/typed/headers/content_security_policy_header.dart @@ -8,20 +8,23 @@ import '../../extension/string_list_extensions.dart'; /// This class manages CSP directives, providing functionality to parse, add, /// remove, and generate CSP header values. final class ContentSecurityPolicyHeader { - static const codec = - HeaderCodec.single(ContentSecurityPolicyHeader.parse, __encode); - static List __encode(final ContentSecurityPolicyHeader value) => - [value._encode()]; + static const codec = HeaderCodec.single( + ContentSecurityPolicyHeader.parse, + __encode, + ); + static List __encode(final ContentSecurityPolicyHeader value) => [ + value._encode(), + ]; /// A list of CSP directives. final List directives; /// Constructs a [ContentSecurityPolicyHeader] instance with the specified /// directives. - ContentSecurityPolicyHeader( - {required final List directives}) - : assert(directives.isNotEmpty), - directives = List.unmodifiable(directives); + ContentSecurityPolicyHeader({ + required final List directives, + }) : assert(directives.isNotEmpty), + directives = List.unmodifiable(directives); /// Parses a CSP header value and returns a [ContentSecurityPolicyHeader] /// instance. @@ -37,14 +40,11 @@ final class ContentSecurityPolicyHeader { final directiveSeparator = RegExp(r'\s+'); final directives = splitValues.map((final part) { - final directiveParts = part.split(directiveSeparator); - final name = directiveParts.first; - final values = directiveParts.skip(1).toList(); - return ContentSecurityPolicyDirective( - name: name, - values: values, - ); - }).toList(); + final directiveParts = part.split(directiveSeparator); + final name = directiveParts.first; + final values = directiveParts.skip(1).toList(); + return ContentSecurityPolicyDirective(name: name, values: values); + }).toList(); return ContentSecurityPolicyHeader(directives: directives); } @@ -60,8 +60,10 @@ final class ContentSecurityPolicyHeader { bool operator ==(final Object other) => identical(this, other) || other is ContentSecurityPolicyHeader && - const ListEquality() - .equals(directives, other.directives); + const ListEquality().equals( + directives, + other.directives, + ); @override int get hashCode => @@ -84,10 +86,7 @@ class ContentSecurityPolicyDirective { /// Constructs a [ContentSecurityPolicyDirective] instance with the specified /// name and values. - ContentSecurityPolicyDirective({ - required this.name, - required this.values, - }); + ContentSecurityPolicyDirective({required this.name, required this.values}); /// Converts the [ContentSecurityPolicyDirective] instance into a string /// representation. diff --git a/lib/src/headers/typed/headers/cookie_header.dart b/lib/src/headers/typed/headers/cookie_header.dart index c4c9dd52..ca2bf535 100644 --- a/lib/src/headers/typed/headers/cookie_header.dart +++ b/lib/src/headers/typed/headers/cookie_header.dart @@ -15,8 +15,8 @@ final class CookieHeader { /// Constructs a [CookieHeader] instance with the specified cookies. CookieHeader({required final List cookies}) - : assert(cookies.isNotEmpty), - cookies = List.unmodifiable(cookies); + : assert(cookies.isNotEmpty), + cookies = List.unmodifiable(cookies); /// Parses the Cookie header value and returns a [CookieHeader] instance. /// @@ -35,7 +35,8 @@ final class CookieHeader { if (names.length != uniqueNames.length) { throw const FormatException( - 'Supplied multiple Name and Value attributes'); + 'Supplied multiple Name and Value attributes', + ); } return CookieHeader(cookies: cookies); @@ -75,11 +76,9 @@ class Cookie { /// The value of the cookie. final String value; - Cookie({ - required final String name, - required final String value, - }) : name = validateCookieName(name), - value = validateCookieValue(value); + Cookie({required final String name, required final String value}) + : name = validateCookieName(name), + value = validateCookieValue(value); factory Cookie.parse(final String value) { final splitValue = value.split('='); @@ -87,10 +86,7 @@ class Cookie { throw const FormatException('Invalid cookie format'); } - return Cookie( - name: splitValue.first.trim(), - value: splitValue.last.trim(), - ); + return Cookie(name: splitValue.first.trim(), value: splitValue.last.trim()); } /// Converts the [Cookie] instance into a string representation suitable for HTTP headers. diff --git a/lib/src/headers/typed/headers/cross_origin_embedder_policy_header.dart b/lib/src/headers/typed/headers/cross_origin_embedder_policy_header.dart index 495bbd4d..1459adfd 100644 --- a/lib/src/headers/typed/headers/cross_origin_embedder_policy_header.dart +++ b/lib/src/headers/typed/headers/cross_origin_embedder_policy_header.dart @@ -4,10 +4,13 @@ import '../../../../relic.dart'; /// /// This header specifies the policy for embedding cross-origin resources. final class CrossOriginEmbedderPolicyHeader { - static const codec = - HeaderCodec.single(CrossOriginEmbedderPolicyHeader.parse, __encode); - static List __encode(final CrossOriginEmbedderPolicyHeader value) => - [value._encode()]; + static const codec = HeaderCodec.single( + CrossOriginEmbedderPolicyHeader.parse, + __encode, + ); + static List __encode(final CrossOriginEmbedderPolicyHeader value) => [ + value._encode(), + ]; /// The policy value of the header. final String policy; @@ -22,8 +25,9 @@ final class CrossOriginEmbedderPolicyHeader { static const unsafeNone = CrossOriginEmbedderPolicyHeader._(_unsafeNone); static const requireCorp = CrossOriginEmbedderPolicyHeader._(_requireCorp); - static const credentialless = - CrossOriginEmbedderPolicyHeader._(_credentialless); + static const credentialless = CrossOriginEmbedderPolicyHeader._( + _credentialless, + ); /// Parses a [value] and returns the corresponding [CrossOriginEmbedderPolicyHeader] instance. /// If the value does not match any predefined types, it returns a custom instance. diff --git a/lib/src/headers/typed/headers/cross_origin_opener_policy_header.dart b/lib/src/headers/typed/headers/cross_origin_opener_policy_header.dart index dd60ab34..6139a2b9 100644 --- a/lib/src/headers/typed/headers/cross_origin_opener_policy_header.dart +++ b/lib/src/headers/typed/headers/cross_origin_opener_policy_header.dart @@ -4,10 +4,13 @@ import '../../../../relic.dart'; /// /// This header specifies the policy for opening cross-origin resources. final class CrossOriginOpenerPolicyHeader { - static const codec = - HeaderCodec.single(CrossOriginOpenerPolicyHeader.parse, __encode); - static List __encode(final CrossOriginOpenerPolicyHeader value) => - [value._encode()]; + static const codec = HeaderCodec.single( + CrossOriginOpenerPolicyHeader.parse, + __encode, + ); + static List __encode(final CrossOriginOpenerPolicyHeader value) => [ + value._encode(), + ]; /// The policy value of the header. final String policy; @@ -21,8 +24,9 @@ final class CrossOriginOpenerPolicyHeader { static const _unsafeNone = 'unsafe-none'; static const sameOrigin = CrossOriginOpenerPolicyHeader._(_sameOrigin); - static const sameOriginAllowPopups = - CrossOriginOpenerPolicyHeader._(_sameOriginAllowPopups); + static const sameOriginAllowPopups = CrossOriginOpenerPolicyHeader._( + _sameOriginAllowPopups, + ); static const unsafeNone = CrossOriginOpenerPolicyHeader._(_unsafeNone); /// Parses a [value] and returns the corresponding [CrossOriginOpenerPolicyHeader] instance. diff --git a/lib/src/headers/typed/headers/cross_origin_resource_policy_header.dart b/lib/src/headers/typed/headers/cross_origin_resource_policy_header.dart index ab788c1c..20dfccb6 100644 --- a/lib/src/headers/typed/headers/cross_origin_resource_policy_header.dart +++ b/lib/src/headers/typed/headers/cross_origin_resource_policy_header.dart @@ -4,10 +4,13 @@ import '../../../../relic.dart'; /// /// This header specifies the policy for sharing resources across origins. final class CrossOriginResourcePolicyHeader { - static const codec = - HeaderCodec.single(CrossOriginResourcePolicyHeader.parse, __encode); - static List __encode(final CrossOriginResourcePolicyHeader value) => - [value._encode()]; + static const codec = HeaderCodec.single( + CrossOriginResourcePolicyHeader.parse, + __encode, + ); + static List __encode(final CrossOriginResourcePolicyHeader value) => [ + value._encode(), + ]; /// The policy value of the header. final String policy; diff --git a/lib/src/headers/typed/headers/etag_header.dart b/lib/src/headers/typed/headers/etag_header.dart index 74329518..7721b4aa 100644 --- a/lib/src/headers/typed/headers/etag_header.dart +++ b/lib/src/headers/typed/headers/etag_header.dart @@ -16,10 +16,7 @@ final class ETagHeader { final bool isWeak; /// Constructs an [ETagHeader] instance with the specified value and whether it is weak. - const ETagHeader({ - required this.value, - this.isWeak = false, - }); + const ETagHeader({required this.value, this.isWeak = false}); /// Predefined ETag prefixes. static const _weakPrefix = 'W/'; diff --git a/lib/src/headers/typed/headers/forwarded_header.dart b/lib/src/headers/typed/headers/forwarded_header.dart index ae5c0362..8d25a10f 100644 --- a/lib/src/headers/typed/headers/forwarded_header.dart +++ b/lib/src/headers/typed/headers/forwarded_header.dart @@ -133,9 +133,10 @@ final class ForwardedElement { this.proto, this.host, final Map? extensions, - }) : extensions = extensions != null && extensions.isNotEmpty - ? CaseInsensitiveMap.from(extensions) - : null; + }) : extensions = + extensions != null && extensions.isNotEmpty + ? CaseInsensitiveMap.from(extensions) + : null; @override bool operator ==(final Object other) => @@ -145,21 +146,23 @@ final class ForwardedElement { by == other.by && proto == other.proto && host == other.host && - const MapEquality() - .equals(extensions, other.extensions); + const MapEquality().equals( + extensions, + other.extensions, + ); @override int get hashCode => Object.hashAll([ - forwardedFor, - by, - proto, - host, - ...[ - extensions != null - ? const MapEquality().hash(extensions) - : 0 - ] - ]); + forwardedFor, + by, + proto, + host, + ...[ + extensions != null + ? const MapEquality().hash(extensions) + : 0, + ], + ]); @override String toString() => @@ -182,8 +185,8 @@ final class ForwardedHeader { final List elements; ForwardedHeader(final List elements) - : assert(elements.isNotEmpty), - elements = List.unmodifiable(elements); + : assert(elements.isNotEmpty), + elements = List.unmodifiable(elements); factory ForwardedHeader.parse(final Iterable values) { final splitValues = values.splitTrimAndFilterUnique(); @@ -212,9 +215,10 @@ final class ForwardedHeader { if (parts[0].isEmpty) continue; if (parts[1].isEmpty) continue; - final key = parts[0] - .trim() - .toLowerCase(); // Parameter names are case-insensitive + final key = + parts[0] + .trim() + .toLowerCase(); // Parameter names are case-insensitive String value = parts[1].trim(); value = _unquote(value); // Unquote if it's a quoted-string @@ -242,12 +246,15 @@ final class ForwardedHeader { protoStr != null || hostStr != null || extensionsMap.isNotEmpty) { - allElements.add(ForwardedElement( + allElements.add( + ForwardedElement( forwardedFor: forNode, by: byNode, proto: protoStr, host: hostStr, - extensions: extensionsMap)); + extensions: extensionsMap, + ), + ); } } @@ -295,8 +302,10 @@ final class ForwardedHeader { bool operator ==(final Object other) => identical(this, other) || other is ForwardedHeader && - const ListEquality() - .equals(elements, other.elements); + const ListEquality().equals( + elements, + other.elements, + ); @override int get hashCode => Object.hashAll(elements); diff --git a/lib/src/headers/typed/headers/from_header.dart b/lib/src/headers/typed/headers/from_header.dart index 95269d08..46054a0f 100644 --- a/lib/src/headers/typed/headers/from_header.dart +++ b/lib/src/headers/typed/headers/from_header.dart @@ -17,8 +17,8 @@ final class FromHeader { /// Private constructor for initializing the [emails] list. FromHeader({required final Iterable emails}) - : assert(emails.isNotEmpty), - emails = List.unmodifiable(emails); + : assert(emails.isNotEmpty), + emails = List.unmodifiable(emails); /// Parses a `From` header value and returns a [FromHeader] instance. factory FromHeader.parse(final Iterable values) { diff --git a/lib/src/headers/typed/headers/if_range_header.dart b/lib/src/headers/typed/headers/if_range_header.dart index 563f1446..299117d0 100644 --- a/lib/src/headers/typed/headers/if_range_header.dart +++ b/lib/src/headers/typed/headers/if_range_header.dart @@ -19,10 +19,7 @@ final class IfRangeHeader { /// Constructs an [IfRangeHeader] instance with either a date or an ETag. /// /// Either [lastModified] or [etag] must be non-null. - IfRangeHeader({ - this.lastModified, - this.etag, - }) { + IfRangeHeader({this.lastModified, this.etag}) { if (lastModified == null && etag == null) { throw const FormatException('Either date or etag must be provided'); } diff --git a/lib/src/headers/typed/headers/permission_policy_header.dart b/lib/src/headers/typed/headers/permission_policy_header.dart index 8232af14..25a4831e 100644 --- a/lib/src/headers/typed/headers/permission_policy_header.dart +++ b/lib/src/headers/typed/headers/permission_policy_header.dart @@ -8,19 +8,22 @@ import '../../extension/string_list_extensions.dart'; /// This class manages Permissions-Policy directives, providing functionality to parse, /// add, remove, and generate Permissions-Policy header values. final class PermissionsPolicyHeader { - static const codec = - HeaderCodec.single(PermissionsPolicyHeader.parse, __encode); - static List __encode(final PermissionsPolicyHeader value) => - [value._encode()]; + static const codec = HeaderCodec.single( + PermissionsPolicyHeader.parse, + __encode, + ); + static List __encode(final PermissionsPolicyHeader value) => [ + value._encode(), + ]; /// A list of Permissions-Policy directives. final List directives; /// Constructs a [PermissionsPolicyHeader] instance with the specified directives. - PermissionsPolicyHeader( - {required final List directives}) - : assert(directives.isNotEmpty), - directives = List.unmodifiable(directives); + PermissionsPolicyHeader({ + required final List directives, + }) : assert(directives.isNotEmpty), + directives = List.unmodifiable(directives); /// Parses the Permissions-Policy header value and returns a [PermissionsPolicyHeader] instance. /// @@ -36,22 +39,18 @@ final class PermissionsPolicyHeader { for (final part in splitValues) { final directiveParts = part.split('='); final name = directiveParts.first.trim(); - final values = directiveParts.length > 1 - ? directiveParts[1] - .replaceAll('(', '') - .replaceAll(')', '') - .split(' ') - .map((final s) => s.trim()) - .where((final s) => s.isNotEmpty) - .toList() - : []; - - directives.add( - PermissionsPolicyDirective( - name: name, - values: values, - ), - ); + final values = + directiveParts.length > 1 + ? directiveParts[1] + .replaceAll('(', '') + .replaceAll(')', '') + .split(' ') + .map((final s) => s.trim()) + .where((final s) => s.isNotEmpty) + .toList() + : []; + + directives.add(PermissionsPolicyDirective(name: name, values: values)); } return PermissionsPolicyHeader(directives: directives); @@ -68,8 +67,10 @@ final class PermissionsPolicyHeader { bool operator ==(final Object other) => identical(this, other) || other is PermissionsPolicyHeader && - const ListEquality() - .equals(directives, other.directives); + const ListEquality().equals( + directives, + other.directives, + ); @override int get hashCode => @@ -90,10 +91,7 @@ class PermissionsPolicyDirective { final Iterable values; /// Constructs a [PermissionsPolicyDirective] instance with the specified name and values. - const PermissionsPolicyDirective({ - required this.name, - required this.values, - }); + const PermissionsPolicyDirective({required this.name, required this.values}); /// Converts the [PermissionsPolicyDirective] instance into a string representation. String _encode() { diff --git a/lib/src/headers/typed/headers/range_header.dart b/lib/src/headers/typed/headers/range_header.dart index 147a5017..3cd31b45 100644 --- a/lib/src/headers/typed/headers/range_header.dart +++ b/lib/src/headers/typed/headers/range_header.dart @@ -21,10 +21,8 @@ final class RangeHeader { /// Constructs a [RangeHeader] instance with the specified unit and list /// of ranges. - RangeHeader({ - this.unit = 'bytes', - required this.ranges, - }) : assert(ranges.isNotEmpty); + RangeHeader({this.unit = 'bytes', required this.ranges}) + : assert(ranges.isNotEmpty); /// Parses the Range header value and returns a [RangeHeader] instance. /// @@ -51,22 +49,23 @@ final class RangeHeader { } final rangeStrings = rangesPart.split(','); - final ranges = rangeStrings.map((final rangeStr) { - final trimmedRange = rangeStr.trim(); - final rangeMatch = RegExp(r'^(\d*)-(\d*)$').firstMatch(trimmedRange); - if (rangeMatch == null) { - throw const FormatException('Invalid range'); - } + final ranges = + rangeStrings.map((final rangeStr) { + final trimmedRange = rangeStr.trim(); + final rangeMatch = RegExp(r'^(\d*)-(\d*)$').firstMatch(trimmedRange); + if (rangeMatch == null) { + throw const FormatException('Invalid range'); + } - final start = int.tryParse(rangeMatch.group(1) ?? ''); - final end = int.tryParse(rangeMatch.group(2) ?? ''); + final start = int.tryParse(rangeMatch.group(1) ?? ''); + final end = int.tryParse(rangeMatch.group(2) ?? ''); - if (start == null && end == null) { - throw const FormatException('Both start and end cannot be empty'); - } + if (start == null && end == null) { + throw const FormatException('Both start and end cannot be empty'); + } - return Range(start: start, end: end); - }).toList(); + return Range(start: start, end: end); + }).toList(); return RangeHeader(unit: unit, ranges: ranges); } @@ -105,13 +104,11 @@ class Range { /// Constructs a [Range] instance with the specified start and end of /// the range. - Range({ - this.start, - this.end, - }) { + Range({this.start, this.end}) { if (start == null && end == null) { throw const FormatException( - 'At least one of start or end must be specified'); + 'At least one of start or end must be specified', + ); } } diff --git a/lib/src/headers/typed/headers/referrer_policy_header.dart b/lib/src/headers/typed/headers/referrer_policy_header.dart index 54c16b33..be66d992 100644 --- a/lib/src/headers/typed/headers/referrer_policy_header.dart +++ b/lib/src/headers/typed/headers/referrer_policy_header.dart @@ -6,8 +6,9 @@ import '../../../../relic.dart'; /// and generate referrer policy header values. final class ReferrerPolicyHeader { static const codec = HeaderCodec.single(ReferrerPolicyHeader.parse, __encode); - static List __encode(final ReferrerPolicyHeader value) => - [value._encode()]; + static List __encode(final ReferrerPolicyHeader value) => [ + value._encode(), + ]; /// The string representation of the referrer policy directive. final String directive; @@ -26,15 +27,18 @@ final class ReferrerPolicyHeader { static const _unsafeUrl = 'unsafe-url'; static const noReferrer = ReferrerPolicyHeader._(_noReferrer); - static const noReferrerWhenDowngrade = - ReferrerPolicyHeader._(_noReferrerWhenDowngrade); + static const noReferrerWhenDowngrade = ReferrerPolicyHeader._( + _noReferrerWhenDowngrade, + ); static const origin = ReferrerPolicyHeader._(_origin); - static const originWhenCrossOrigin = - ReferrerPolicyHeader._(_originWhenCrossOrigin); + static const originWhenCrossOrigin = ReferrerPolicyHeader._( + _originWhenCrossOrigin, + ); static const sameOrigin = ReferrerPolicyHeader._(_sameOrigin); static const strictOrigin = ReferrerPolicyHeader._(_strictOrigin); - static const strictOriginWhenCrossOrigin = - ReferrerPolicyHeader._(_strictOriginWhenCrossOrigin); + static const strictOriginWhenCrossOrigin = ReferrerPolicyHeader._( + _strictOriginWhenCrossOrigin, + ); static const unsafeUrl = ReferrerPolicyHeader._(_unsafeUrl); /// Parses a [directive] and returns the corresponding [ReferrerPolicyHeader] instance. diff --git a/lib/src/headers/typed/headers/retry_after_header.dart b/lib/src/headers/typed/headers/retry_after_header.dart index edf7b22c..46571daa 100644 --- a/lib/src/headers/typed/headers/retry_after_header.dart +++ b/lib/src/headers/typed/headers/retry_after_header.dart @@ -8,8 +8,9 @@ import '../../../../relic.dart'; /// indicating when the client should retry the request. final class RetryAfterHeader { static const codec = HeaderCodec.single(RetryAfterHeader.parse, __encode); - static List __encode(final RetryAfterHeader value) => - [value._encode()]; + static List __encode(final RetryAfterHeader value) => [ + value._encode(), + ]; /// The retry delay in seconds, if present. final int? delay; @@ -18,14 +19,9 @@ final class RetryAfterHeader { final DateTime? date; /// Constructs a [RetryAfterHeader] instance with either a delay in seconds or a date. - RetryAfterHeader({ - this.delay, - this.date, - }) { + RetryAfterHeader({this.delay, this.date}) { if (delay == null && date == null) { - throw const FormatException( - 'Either delay or date must be specified', - ); + throw const FormatException('Either delay or date must be specified'); } if (delay != null && date != null) { throw const FormatException( diff --git a/lib/src/headers/typed/headers/sec_fetch_dest_header.dart b/lib/src/headers/typed/headers/sec_fetch_dest_header.dart index a4a399f7..fb4f77d8 100644 --- a/lib/src/headers/typed/headers/sec_fetch_dest_header.dart +++ b/lib/src/headers/typed/headers/sec_fetch_dest_header.dart @@ -5,8 +5,9 @@ import '../../../../relic.dart'; /// This header indicates the destination of the request. final class SecFetchDestHeader { static const codec = HeaderCodec.single(SecFetchDestHeader.parse, __encode); - static List __encode(final SecFetchDestHeader value) => - [value._encode()]; + static List __encode(final SecFetchDestHeader value) => [ + value._encode(), + ]; /// The destination value of the request. final String destination; diff --git a/lib/src/headers/typed/headers/sec_fetch_mode_header.dart b/lib/src/headers/typed/headers/sec_fetch_mode_header.dart index a11a3c53..33b64d03 100644 --- a/lib/src/headers/typed/headers/sec_fetch_mode_header.dart +++ b/lib/src/headers/typed/headers/sec_fetch_mode_header.dart @@ -5,8 +5,9 @@ import '../../../../relic.dart'; /// This header indicates the mode of the request. final class SecFetchModeHeader { static const codec = HeaderCodec.single(SecFetchModeHeader.parse, __encode); - static List __encode(final SecFetchModeHeader value) => - [value._encode()]; + static List __encode(final SecFetchModeHeader value) => [ + value._encode(), + ]; /// The mode value of the request. final String mode; diff --git a/lib/src/headers/typed/headers/sec_fetch_site_header.dart b/lib/src/headers/typed/headers/sec_fetch_site_header.dart index 50119f82..4c7d0804 100644 --- a/lib/src/headers/typed/headers/sec_fetch_site_header.dart +++ b/lib/src/headers/typed/headers/sec_fetch_site_header.dart @@ -6,8 +6,9 @@ import '../../../../relic.dart'; /// initiator and the origin of the requested resource. final class SecFetchSiteHeader { static const codec = HeaderCodec.single(SecFetchSiteHeader.parse, __encode); - static List __encode(final SecFetchSiteHeader value) => - [value._encode()]; + static List __encode(final SecFetchSiteHeader value) => [ + value._encode(), + ]; /// The site value of the request. final String site; diff --git a/lib/src/headers/typed/headers/set_cookie_header.dart b/lib/src/headers/typed/headers/set_cookie_header.dart index 8c79c77b..3f8f272d 100644 --- a/lib/src/headers/typed/headers/set_cookie_header.dart +++ b/lib/src/headers/typed/headers/set_cookie_header.dart @@ -10,8 +10,9 @@ import 'util/cookie_util.dart'; /// This class manages the parsing and representation of set cookie. final class SetCookieHeader { static const codec = HeaderCodec.single(SetCookieHeader.parse, __encode); - static List __encode(final SetCookieHeader value) => - [value._encode()]; + static List __encode(final SetCookieHeader value) => [ + value._encode(), + ]; /// The keys used for the Set-Cookie header. static const String _expires = 'Expires='; @@ -66,8 +67,8 @@ final class SetCookieHeader { this.secure = false, this.httpOnly = false, this.sameSite, - }) : name = validateCookieName(name), - value = validateCookieValue(value); + }) : name = validateCookieName(name), + value = validateCookieValue(value); factory SetCookieHeader.parse(final String value) { final splitValue = value.splitTrimAndFilterUnique(separator: ';'); @@ -95,8 +96,8 @@ final class SetCookieHeader { sameSite = SameSite.values.firstWhere( (final sameSite) => sameSite.name.toLowerCase() == samesiteValue.toLowerCase(), - orElse: () => - throw const FormatException('Invalid SameSite attribute'), + orElse: + () => throw const FormatException('Invalid SameSite attribute'), ); continue; } @@ -158,7 +159,8 @@ final class SetCookieHeader { if (cookie.contains('=')) { if (cookieName.isNotEmpty || cookieValue.isNotEmpty) { throw const FormatException( - 'Supplied multiple Name and Value attributes'); + 'Supplied multiple Name and Value attributes', + ); } final parts = cookie.split('='); cookieName = parts.first.trim(); @@ -218,16 +220,16 @@ final class SetCookieHeader { @override int get hashCode => Object.hashAll([ - name, - value, - expires, - maxAge, - domain, - path, - secure, - httpOnly, - sameSite, - ]); + name, + value, + expires, + maxAge, + domain, + path, + secure, + httpOnly, + sameSite, + ]); @override String toString() { diff --git a/lib/src/headers/typed/headers/strict_transport_security_header.dart b/lib/src/headers/typed/headers/strict_transport_security_header.dart index 14f2526a..af5db990 100644 --- a/lib/src/headers/typed/headers/strict_transport_security_header.dart +++ b/lib/src/headers/typed/headers/strict_transport_security_header.dart @@ -4,10 +4,13 @@ import '../../extension/string_list_extensions.dart'; /// Represents the HTTP Strict-Transport-Security (HSTS) header for managing /// HSTS settings. final class StrictTransportSecurityHeader { - static const codec = - HeaderCodec.single(StrictTransportSecurityHeader.parse, __encode); - static List __encode(final StrictTransportSecurityHeader value) => - [value._encode()]; + static const codec = HeaderCodec.single( + StrictTransportSecurityHeader.parse, + __encode, + ); + static List __encode(final StrictTransportSecurityHeader value) => [ + value._encode(), + ]; /// The max-age directive specifies the time, in seconds, that the browser /// should remember that a site is only to be accessed using HTTPS. diff --git a/lib/src/headers/typed/headers/te_header.dart b/lib/src/headers/typed/headers/te_header.dart index 82eeea27..1319a9dc 100644 --- a/lib/src/headers/typed/headers/te_header.dart +++ b/lib/src/headers/typed/headers/te_header.dart @@ -16,8 +16,8 @@ final class TEHeader { /// Constructs a [TEHeader] instance with the specified list of encodings. TEHeader({required final List encodings}) - : assert(encodings.isNotEmpty), - encodings = List.unmodifiable(encodings); + : assert(encodings.isNotEmpty), + encodings = List.unmodifiable(encodings); /// Parses the TE header value and returns a [TEHeader] instance. /// @@ -30,22 +30,23 @@ final class TEHeader { throw const FormatException('Value cannot be empty'); } - final encodings = splitValues.map((final value) { - final encodingParts = value.split(';q='); - final encoding = encodingParts[0].trim().toLowerCase(); - if (encoding.isEmpty) { - throw const FormatException('Invalid encoding'); - } - double? quality; - if (encodingParts.length > 1) { - final value = double.tryParse(encodingParts[1].trim()); - if (value == null || value < 0 || value > 1) { - throw const FormatException('Invalid quality value'); - } - quality = value; - } - return TeQuality(encoding, quality); - }).toList(); + final encodings = + splitValues.map((final value) { + final encodingParts = value.split(';q='); + final encoding = encodingParts[0].trim().toLowerCase(); + if (encoding.isEmpty) { + throw const FormatException('Invalid encoding'); + } + double? quality; + if (encodingParts.length > 1) { + final value = double.tryParse(encodingParts[1].trim()); + if (value == null || value < 0 || value > 1) { + throw const FormatException('Invalid quality value'); + } + quality = value; + } + return TeQuality(encoding, quality); + }).toList(); return TEHeader(encodings: encodings); } diff --git a/lib/src/headers/typed/headers/transfer_encoding_header.dart b/lib/src/headers/typed/headers/transfer_encoding_header.dart index e4039d37..535ef0ce 100644 --- a/lib/src/headers/typed/headers/transfer_encoding_header.dart +++ b/lib/src/headers/typed/headers/transfer_encoding_header.dart @@ -8,16 +8,16 @@ import '../../extension/string_list_extensions.dart'; /// It provides functionality to parse and generate transfer encoding header values. final class TransferEncodingHeader { static const codec = HeaderCodec(TransferEncodingHeader.parse, __encode); - static List __encode(final TransferEncodingHeader value) => - [value._encode()]; + static List __encode(final TransferEncodingHeader value) => [ + value._encode(), + ]; /// A list of transfer encodings. final List encodings; /// Constructs a [TransferEncodingHeader] instance with the specified transfer encodings. - TransferEncodingHeader({ - required final List encodings, - }) : encodings = List.unmodifiable(_reorderEncodings(encodings)) { + TransferEncodingHeader({required final List encodings}) + : encodings = List.unmodifiable(_reorderEncodings(encodings)) { if (encodings.isEmpty) { throw ArgumentError.value(encodings, 'encodings', 'cannot be empty'); } @@ -45,8 +45,10 @@ final class TransferEncodingHeader { bool operator ==(final Object other) { if (identical(this, other)) return true; if (other is! TransferEncodingHeader) return false; - return const ListEquality() - .equals(encodings, other.encodings); + return const ListEquality().equals( + encodings, + other.encodings, + ); } @override diff --git a/lib/src/headers/typed/headers/upgrade_header.dart b/lib/src/headers/typed/headers/upgrade_header.dart index 060557f0..c90b9bee 100644 --- a/lib/src/headers/typed/headers/upgrade_header.dart +++ b/lib/src/headers/typed/headers/upgrade_header.dart @@ -16,8 +16,8 @@ final class UpgradeHeader { /// Constructs an [UpgradeHeader] instance with the specified protocols. UpgradeHeader({required final List protocols}) - : assert(protocols.isNotEmpty), - protocols = List.unmodifiable(protocols); + : assert(protocols.isNotEmpty), + protocols = List.unmodifiable(protocols); /// Parses the Upgrade header value and returns an [UpgradeHeader] instance. /// @@ -28,9 +28,10 @@ final class UpgradeHeader { throw const FormatException('Value cannot be empty'); } - final protocols = splitValues - .map((final protocol) => UpgradeProtocol.parse(protocol)) - .toList(); + final protocols = + splitValues + .map((final protocol) => UpgradeProtocol.parse(protocol)) + .toList(); return UpgradeHeader(protocols: protocols); } @@ -46,8 +47,10 @@ final class UpgradeHeader { bool operator ==(final Object other) => identical(this, other) || other is UpgradeHeader && - const ListEquality() - .equals(protocols, other.protocols); + const ListEquality().equals( + protocols, + other.protocols, + ); @override int get hashCode => const ListEquality().hash(protocols); @@ -67,10 +70,7 @@ class UpgradeProtocol { final double? version; /// Constructs an [UpgradeProtocol] instance with the specified name and version. - UpgradeProtocol({ - required this.protocol, - this.version, - }); + UpgradeProtocol({required this.protocol, this.version}); /// Parses a protocol string and returns an [UpgradeProtocol] instance. factory UpgradeProtocol.parse(final String value) { @@ -99,10 +99,7 @@ class UpgradeProtocol { throw const FormatException('Invalid version'); } - return UpgradeProtocol( - protocol: protocol, - version: parsedVersion, - ); + return UpgradeProtocol(protocol: protocol, version: parsedVersion); } /// Converts the [UpgradeProtocol] instance into a string representation. diff --git a/lib/src/headers/typed/headers/util/cookie_util.dart b/lib/src/headers/typed/headers/util/cookie_util.dart index af0dc5ce..7eb185fb 100644 --- a/lib/src/headers/typed/headers/util/cookie_util.dart +++ b/lib/src/headers/typed/headers/util/cookie_util.dart @@ -21,7 +21,7 @@ String validateCookieName(final String name) { '?', '=', '{', - '}' + '}', }; for (int i = 0; i < name.length; i++) { @@ -29,12 +29,12 @@ String validateCookieName(final String name) { // Disallow control characters, non-ASCII characters, and reserved separators if ( - // Disallows ASCII control characters (code points 0-32), including spaces, tabs, and other non-printable characters - codeUnit <= 32 || - // Disallows non-ASCII characters (code points 127 and above), ensuring only standard ASCII is used - codeUnit >= 127 || - // Disallows reserved separator characters [separators], based on RFC 6265 - separators.contains(name[i])) { + // Disallows ASCII control characters (code points 0-32), including spaces, tabs, and other non-printable characters + codeUnit <= 32 || + // Disallows non-ASCII characters (code points 127 and above), ensuring only standard ASCII is used + codeUnit >= 127 || + // Disallows reserved separator characters [separators], based on RFC 6265 + separators.contains(name[i])) { throw const FormatException('Invalid cookie name'); } } @@ -65,16 +65,16 @@ String validateCookieValue(final String value) { final int codeUnit = value.codeUnitAt(i); if (!( - // '!' (ASCII 33) is allowed - codeUnit == 0x21 || - // '#' (35) to '+' (43) are allowed, including symbols like '#', '$', '%', '&', and '+' - (codeUnit >= 0x23 && codeUnit <= 0x2B) || - // '-' (45) to ':' (58) are allowed, covering '-', '.', '/', '0-9', and ':' - (codeUnit >= 0x2D && codeUnit <= 0x3A) || - // '<' (60) to '[' (91) are allowed, covering '<', '=', '>', '?', '@', 'A-Z', and '[' - (codeUnit >= 0x3C && codeUnit <= 0x5B) || - // ']' (93) to '~' (126) are allowed, covering ']', '^', '_', '`', 'a-z', '{', '|', '}', and '~' - (codeUnit >= 0x5D && codeUnit <= 0x7E))) { + // '!' (ASCII 33) is allowed + codeUnit == 0x21 || + // '#' (35) to '+' (43) are allowed, including symbols like '#', '$', '%', '&', and '+' + (codeUnit >= 0x23 && codeUnit <= 0x2B) || + // '-' (45) to ':' (58) are allowed, covering '-', '.', '/', '0-9', and ':' + (codeUnit >= 0x2D && codeUnit <= 0x3A) || + // '<' (60) to '[' (91) are allowed, covering '<', '=', '>', '?', '@', 'A-Z', and '[' + (codeUnit >= 0x3C && codeUnit <= 0x5B) || + // ']' (93) to '~' (126) are allowed, covering ']', '^', '_', '`', 'a-z', '{', '|', '}', and '~' + (codeUnit >= 0x5D && codeUnit <= 0x7E))) { throw const FormatException('Invalid cookie value'); } } diff --git a/lib/src/headers/typed/headers/vary_header.dart b/lib/src/headers/typed/headers/vary_header.dart index 85a49c27..a1a21281 100644 --- a/lib/src/headers/typed/headers/vary_header.dart +++ b/lib/src/headers/typed/headers/vary_header.dart @@ -41,16 +41,15 @@ final class VaryHeader { if (splitValues.length > 1 && splitValues.contains('*')) { throw const FormatException( - 'Wildcard (*) cannot be used with other values'); + 'Wildcard (*) cannot be used with other values', + ); } return VaryHeader.headers(fields: splitValues); } /// Constructs an instance allowing all headers to vary (`*`). - VaryHeader.wildcard() - : fields = const [], - isWildcard = true; + VaryHeader.wildcard() : fields = const [], isWildcard = true; @override bool operator ==(final Object other) { diff --git a/lib/src/headers/typed/headers/wildcard_list_header.dart b/lib/src/headers/typed/headers/wildcard_list_header.dart index 425bab7d..5bec3f01 100644 --- a/lib/src/headers/typed/headers/wildcard_list_header.dart +++ b/lib/src/headers/typed/headers/wildcard_list_header.dart @@ -19,7 +19,7 @@ class WildcardListHeader { /// /// Passing an empty list will raise an ArgumentError WildcardListHeader(final List values) - : values = List.unmodifiable(values) { + : values = List.unmodifiable(values) { if (values.isEmpty) { throw ArgumentError.value(values, 'values', 'Cannot be empty'); } @@ -45,7 +45,8 @@ class WildcardListHeader { if (splitValues.length > 1 && splitValues.contains('*')) { throw const FormatException( - 'Wildcard (*) cannot be used with other values'); + 'Wildcard (*) cannot be used with other values', + ); } final parsedValues = splitValues.map(parseElement).toList(); diff --git a/lib/src/headers/typed/headers/x_forwarded_for_header.dart b/lib/src/headers/typed/headers/x_forwarded_for_header.dart index b8e1edfc..241c5258 100644 --- a/lib/src/headers/typed/headers/x_forwarded_for_header.dart +++ b/lib/src/headers/typed/headers/x_forwarded_for_header.dart @@ -20,7 +20,7 @@ final class XForwardedForHeader { /// Creates an [XForwardedForHeader] with the given list of addresses. XForwardedForHeader(final Iterable addresses) - : addresses = List.unmodifiable(addresses) { + : addresses = List.unmodifiable(addresses) { if (addresses.isEmpty) { throw ArgumentError.value(addresses, 'addresses', 'cannot be empty'); } @@ -41,8 +41,9 @@ final class XForwardedForHeader { __encode, ); - static List __encode(final XForwardedForHeader value) => - [value._encode()]; + static List __encode(final XForwardedForHeader value) => [ + value._encode(), + ]; String _encode() => addresses.join(', '); @override diff --git a/lib/src/io/static/cache_busting_config.dart b/lib/src/io/static/cache_busting_config.dart index 6dbb2165..45888ba6 100644 --- a/lib/src/io/static/cache_busting_config.dart +++ b/lib/src/io/static/cache_busting_config.dart @@ -34,8 +34,8 @@ class CacheBustingConfig { required final String mountPrefix, required final Directory fileSystemRoot, this.separator = '@', - }) : mountPrefix = _normalizeMount(mountPrefix), - fileSystemRoot = fileSystemRoot.absolute { + }) : mountPrefix = _normalizeMount(mountPrefix), + fileSystemRoot = fileSystemRoot.absolute { _validateFileSystemRoot(fileSystemRoot.absolute); _validateSeparator(separator); } @@ -62,8 +62,10 @@ class CacheBustingConfig { } // Ensure target exists (files only) before resolving symlinks - final entityType = - FileSystemEntity.typeSync(normalizedPath, followLinks: false); + final entityType = FileSystemEntity.typeSync( + normalizedPath, + followLinks: false, + ); if (entityType == FileSystemEntityType.notFound || entityType == FileSystemEntityType.directory) { throw PathNotFoundException( @@ -121,11 +123,7 @@ void _validateFileSystemRoot(final Directory dir) { final resolved = dir.absolute.resolveSymbolicLinksSync(); final entityType = FileSystemEntity.typeSync(resolved); if (entityType != FileSystemEntityType.directory) { - throw ArgumentError.value( - dir.path, - 'fileSystemRoot', - 'is not a directory', - ); + throw ArgumentError.value(dir.path, 'fileSystemRoot', 'is not a directory'); } } @@ -149,9 +147,7 @@ extension CacheBustingFilenameExtension on CacheBustingConfig { /// `logo@abc.png` -> `logo.png` /// `logo@abc` -> `logo` /// `logo.png` -> `logo.png` (no change) - String tryStripHashFromFilename( - final String fileName, - ) { + String tryStripHashFromFilename(final String fileName) { final ext = p.url.extension(fileName); final base = p.url.basenameWithoutExtension(fileName); diff --git a/lib/src/io/static/static_handler.dart b/lib/src/io/static/static_handler.dart index 71e42425..2eecfbce 100644 --- a/lib/src/io/static/static_handler.dart +++ b/lib/src/io/static/static_handler.dart @@ -31,10 +31,8 @@ class FileInfo { const FileInfo(this.file, this.mimeType, this.stat, this.etag); } -typedef CacheControlFactory = CacheControlHeader? Function( - NewContext ctx, - FileInfo fileInfo, -); +typedef CacheControlFactory = + CacheControlHeader? Function(NewContext ctx, FileInfo fileInfo); /// LRU cache for file information to avoid repeated file system operations. final _fileInfoCache = LruCache(10000); @@ -46,8 +44,7 @@ final _fileInfoCache = LruCache(10000); Future getStaticFileInfo( final File file, { final MimeTypeResolver? mimeResolver, -}) async => - _getFileInfo(file, mimeResolver ?? _defaultMimeTypeResolver); +}) async => _getFileInfo(file, mimeResolver ?? _defaultMimeTypeResolver); /// A [HandlerObject] that serves static files from a directory or a single file. /// @@ -134,7 +131,7 @@ class StaticHandler extends HandlerObject { Directory() => _handleDirectory(ctx, entity as Directory), File() => _handleFile(ctx, entity as File), // coverage: ignore-line - _ => throw StateError('Unsupported entity type: ${entity.runtimeType}') + _ => throw StateError('Unsupported entity type: ${entity.runtimeType}'), }; } @@ -144,31 +141,33 @@ class StaticHandler extends HandlerObject { ) async { final resolvedRootPath = directory.resolveSymbolicLinksSync(); final fallbackHandler = - defaultHandler ?? respondWith((final _) => Response.notFound()); + defaultHandler ?? respondWith((_) => Response.notFound()); final resolveFilePath = switch (cacheBustingConfig) { null => (final String resolvedRootPath, final List requestSegments) => p.joinAll([resolvedRootPath, ...requestSegments]), - final cfg => - (final String resolvedRootPath, final List requestSegments) { - if (requestSegments.isEmpty) { - return resolvedRootPath; - } - - final fileName = cfg.tryStripHashFromFilename( - requestSegments.last, - ); - return p.joinAll([ - resolvedRootPath, - ...requestSegments.sublist(0, requestSegments.length - 1), - fileName, - ]); + final cfg => ( + final String resolvedRootPath, + final List requestSegments, + ) { + if (requestSegments.isEmpty) { + return resolvedRootPath; } + + final fileName = cfg.tryStripHashFromFilename(requestSegments.last); + return p.joinAll([ + resolvedRootPath, + ...requestSegments.sublist(0, requestSegments.length - 1), + fileName, + ]); + }, }; - final filePath = - resolveFilePath(resolvedRootPath, ctx.remainingPath.segments); + final filePath = resolveFilePath( + resolvedRootPath, + ctx.remainingPath.segments, + ); // Ensure file exists and is not a directory final entityType = FileSystemEntity.typeSync(filePath, followLinks: false); @@ -242,13 +241,14 @@ bool _isMethodAllowed(final Method method) { /// Returns a 405 Method Not Allowed response. ResponseContext _methodNotAllowedResponse(final NewContext ctx) { - return ctx.respond(Response( - HttpStatus.methodNotAllowed, - headers: Headers.build((final mh) => mh.allow = { - Method.get, - Method.head, - }), - )); + return ctx.respond( + Response( + HttpStatus.methodNotAllowed, + headers: Headers.build( + (final mh) => mh.allow = {Method.get, Method.head}, + ), + ), + ); } /// Gets file information from cache or creates new cache entry. @@ -264,10 +264,9 @@ Future _getFileInfo( if (cachedInfo != null && !cachedInfo.isStale(stat)) return cachedInfo; // Generate new file info - final (etag, mimeType) = await Isolate.run(() => ( - _generateETag(file), - _detectMimeType(file, mimeResolver), - ).wait); + final (etag, mimeType) = await Isolate.run( + () => (_generateETag(file), _detectMimeType(file, mimeResolver)).wait, + ); final fileInfo = FileInfo(file, mimeType, stat, etag); _fileInfoCache[file.path] = fileInfo; @@ -282,11 +281,14 @@ Future _generateETag(final File file) async { /// Detects MIME type using file path and content magic numbers. Future _detectMimeType( - final File file, final MimeTypeResolver mimeResolver) async { - final headerBytes = await file - .openRead(0, mimeResolver.magicNumbersMaxLength) - .cast() - .firstOrNull; + final File file, + final MimeTypeResolver mimeResolver, +) async { + final headerBytes = + await file + .openRead(0, mimeResolver.magicNumbersMaxLength) + .cast() + .firstOrNull; final mimeString = mimeResolver.lookup(file.path, headerBytes: headerBytes); return mimeString != null ? MimeType.parse(mimeString) : null; @@ -294,12 +296,17 @@ Future _detectMimeType( /// Builds base response headers common to all responses. Headers _buildBaseHeaders( - final FileInfo fileInfo, final CacheControlHeader? cacheControl) { - return Headers.build((final mh) => mh - ..acceptRanges = AcceptRangesHeader.bytes() - ..etag = ETagHeader(value: fileInfo.etag) - ..lastModified = fileInfo.stat.modified - ..cacheControl = cacheControl); + final FileInfo fileInfo, + final CacheControlHeader? cacheControl, +) { + return Headers.build( + (final mh) => + mh + ..acceptRanges = AcceptRangesHeader.bytes() + ..etag = ETagHeader(value: fileInfo.etag) + ..lastModified = fileInfo.stat.modified + ..cacheControl = cacheControl, + ); } /// Checks conditional request headers and returns 304 response if appropriate. @@ -377,10 +384,12 @@ ResponseContext _serveFullFile( final Headers headers, final Method method, ) { - return ctx.respond(Response.ok( - headers: headers, - body: _createFileBody(fileInfo, isHeadRequest: method == Method.head), - )); + return ctx.respond( + Response.ok( + headers: headers, + body: _createFileBody(fileInfo, isHeadRequest: method == Method.head), + ), + ); } /// Serves a single range of the file. @@ -397,16 +406,21 @@ ResponseContext _serveSingleRange( return ctx.respond(Response(416, headers: headers)); } - return ctx.respond(Response( - HttpStatus.partialContent, - headers: headers.transform((final mh) => mh - ..contentRange = ContentRangeHeader( - start: start, - end: end - 1, - size: fileInfo.stat.size, - )), - body: _createRangeBody(fileInfo, start, end), - )); + return ctx.respond( + Response( + HttpStatus.partialContent, + headers: headers.transform( + (final mh) => + mh + ..contentRange = ContentRangeHeader( + start: start, + end: end - 1, + size: fileInfo.stat.size, + ), + ), + body: _createRangeBody(fileInfo, start, end), + ), + ); } /// Serves multiple ranges as multipart response. @@ -438,19 +452,24 @@ Future _serveMultipleRanges( unawaited(controller.close()); - return ctx.respond(Response( - HttpStatus.partialContent, - headers: headers.transform((final mh) => mh - ..[Headers.contentTypeHeader] = [ - '${MimeType.multipartByteranges.toHeaderValue()}; boundary=$boundary' - ]), - body: Body.fromDataStream( - controller.stream, - contentLength: totalLength, - mimeType: MimeType.multipartByteranges, - encoding: fileInfo.mimeType?.isText == true ? utf8 : null, + return ctx.respond( + Response( + HttpStatus.partialContent, + headers: headers.transform( + (final mh) => + mh + ..[Headers.contentTypeHeader] = [ + '${MimeType.multipartByteranges.toHeaderValue()}; boundary=$boundary', + ], + ), + body: Body.fromDataStream( + controller.stream, + contentLength: totalLength, + mimeType: MimeType.multipartByteranges, + encoding: fileInfo.mimeType?.isText == true ? utf8 : null, + ), ), - )); + ); } /// Calculates actual start and end positions for a range. @@ -487,7 +506,8 @@ Future _writeMultipartSection( // Write part header final mimeType = fileInfo.mimeType ?? MimeType.octetStream; - final partHeader = '\r\n--$boundary\r\n' + final partHeader = + '\r\n--$boundary\r\n' 'Content-Type: ${mimeType.toHeaderValue()}\r\n' 'Content-Range: bytes $start-${end - 1}/${fileInfo.stat.size}\r\n\r\n'; diff --git a/lib/src/isolated_object.dart b/lib/src/isolated_object.dart index 56639de9..33aca542 100644 --- a/lib/src/isolated_object.dart +++ b/lib/src/isolated_object.dart @@ -15,9 +15,7 @@ class IsolatedObject { IsolatedObject(final Factory create) : _connected = _connect(create); - static Future<_Setup> _connect( - final Factory create, - ) async { + static Future<_Setup> _connect(final Factory create) async { final parentPort = RawReceivePort(); final setupDone = Completer<_Setup>(); @@ -42,27 +40,30 @@ class IsolatedObject { final result = await setupDone.future; final (toChild, fromChild, inflight) = result; - fromChild.asyncListen((final message) async { - if (message case final _Response response) { - final completer = inflight.remove(response.id); - assert(completer != null, 'PROTOCOL BUG. No such ID ${response.id}'); - if (completer == null) return; // coverage: ignore-line - switch (response.result) { - case final RemoteError e: - completer.completeError(e, e.stackTrace); - default: - completer.complete(await response.result); + fromChild.asyncListen( + (final message) async { + if (message case final _Response response) { + final completer = inflight.remove(response.id); + assert(completer != null, 'PROTOCOL BUG. No such ID ${response.id}'); + if (completer == null) return; // coverage: ignore-line + switch (response.result) { + case final RemoteError e: + completer.completeError(e, e.stackTrace); + default: + completer.complete(await response.result); + } } - } - }, onDone: () { - // ReceivePort closed. Fail any pending requests to avoid hangs. - for (final c in inflight.values) { - if (!c.isCompleted) { - c.completeError(StateError('IsolatedObject<$T> channel closed')); + }, + onDone: () { + // ReceivePort closed. Fail any pending requests to avoid hangs. + for (final c in inflight.values) { + if (!c.isCompleted) { + c.completeError(StateError('IsolatedObject<$T> channel closed')); + } } - } - inflight.clear(); - }); + inflight.clear(); + }, + ); return result; } @@ -71,36 +72,33 @@ class IsolatedObject { final Factory create, final SendPort toParent, ) { - return Isolate.spawn( - (final toParent) async { - final childPort = ReceivePort(); - final T isolatedObject; - try { - isolatedObject = await create(); - toParent.send(childPort.sendPort); // complete handshake - } catch (e, st) { - toParent.send(RemoteError('$e', '$st')); - return; - } + return Isolate.spawn((final toParent) async { + final childPort = ReceivePort(); + final T isolatedObject; + try { + isolatedObject = await create(); + toParent.send(childPort.sendPort); // complete handshake + } catch (e, st) { + toParent.send(RemoteError('$e', '$st')); + return; + } - // process inbound actions - await for (final message in childPort) { - if (message == null) { - // shutdown signal received - childPort.close(); - break; - } else if (message case final _Action action) { - try { - final result = await action.function(isolatedObject); - toParent.send((id: action.id, result: result)); // return result - } catch (e, st) { - toParent.send((id: action.id, result: RemoteError('$e', '$st'))); - } + // process inbound actions + await for (final message in childPort) { + if (message == null) { + // shutdown signal received + childPort.close(); + break; + } else if (message case final _Action action) { + try { + final result = await action.function(isolatedObject); + toParent.send((id: action.id, result: result)); // return result + } catch (e, st) { + toParent.send((id: action.id, result: RemoteError('$e', '$st'))); } } - }, - toParent, - ); + } + }, toParent); } int _nextId = 0; diff --git a/lib/src/logger/logger.dart b/lib/src/logger/logger.dart index 61318b7a..65e02488 100644 --- a/lib/src/logger/logger.dart +++ b/lib/src/logger/logger.dart @@ -1,17 +1,10 @@ import 'dart:io'; import 'package:stack_trace/stack_trace.dart'; -typedef Logger = void Function( - String message, { - StackTrace? stackTrace, - LoggerType type, -}); +typedef Logger = + void Function(String message, {StackTrace? stackTrace, LoggerType type}); -enum LoggerType { - error, - warn, - info, -} +enum LoggerType { error, warn, info } /// Logs a message to the standard output or error stream. /// @@ -27,9 +20,12 @@ void logMessage( var chain = Chain.current(); if (stackTrace != null) { - chain = Chain.forTrace(stackTrace) - .foldFrames((final frame) => frame.isCore || frame.package == 'relic') - .terse; + chain = + Chain.forTrace(stackTrace) + .foldFrames( + (final frame) => frame.isCore || frame.package == 'relic', + ) + .terse; } switch (type) { diff --git a/lib/src/message/message.dart b/lib/src/message/message.dart index 8686043c..5b29b490 100644 --- a/lib/src/message/message.dart +++ b/lib/src/message/message.dart @@ -11,10 +11,7 @@ abstract class Message { /// The streaming body of the message. Body body; - Message({ - required this.body, - required this.headers, - }); + Message({required this.body, required this.headers}); /// Returns the MIME type from the Body-Type (Content-Type header), if available. MimeType? get mimeType => body.bodyType?.mimeType; @@ -46,8 +43,5 @@ abstract class Message { bool get isEmpty => body.contentLength == 0; /// Creates a new message by copying existing values and applying specified changes. - Message copyWith({ - final Headers headers, - final Body? body, - }); + Message copyWith({final Headers headers, final Body? body}); } diff --git a/lib/src/message/request.dart b/lib/src/message/request.dart index 5fd7a2b4..314d05be 100644 --- a/lib/src/message/request.dart +++ b/lib/src/message/request.dart @@ -126,14 +126,14 @@ class Request extends Message { final Uri? url, final Body? body, }) : this._( - method, - requestedUri, - headers ?? Headers.empty(), - protocolVersion: protocolVersion, - url: url, - handlerPath: handlerPath, - body: body, - ); + method, + requestedUri, + headers ?? Headers.empty(), + protocolVersion: protocolVersion, + url: url, + handlerPath: handlerPath, + body: body, + ); /// This constructor has the same signature as [Request.new] except that /// accepts [onHijack] as [_OnHijack]. @@ -149,10 +149,10 @@ class Request extends Message { final String? handlerPath, final Uri? url, final Body? body, - }) : protocolVersion = protocolVersion ?? '1.1', - url = _computeUrl(requestedUri, handlerPath, url), - handlerPath = _computeHandlerPath(requestedUri, handlerPath, url), - super(body: body ?? Body.empty(), headers: headers) { + }) : protocolVersion = protocolVersion ?? '1.1', + url = _computeUrl(requestedUri, handlerPath, url), + handlerPath = _computeHandlerPath(requestedUri, handlerPath, url), + super(body: body ?? Body.empty(), headers: headers) { try { // Trigger URI parsing methods that may throw format exception (in Request // constructor or in handlers / routing). @@ -160,17 +160,26 @@ class Request extends Message { requestedUri.queryParametersAll; } on FormatException catch (e) { throw ArgumentError.value( - requestedUri, 'requestedUri', 'URI parsing failed: $e'); + requestedUri, + 'requestedUri', + 'URI parsing failed: $e', + ); } if (!requestedUri.isAbsolute) { throw ArgumentError.value( - requestedUri, 'requestedUri', 'must be an absolute URL.'); + requestedUri, + 'requestedUri', + 'must be an absolute URL.', + ); } if (requestedUri.fragment.isNotEmpty) { throw ArgumentError.value( - requestedUri, 'requestedUri', 'may not have a fragment.'); + requestedUri, + 'requestedUri', + 'may not have a fragment.', + ); } // Notice that because relative paths must encode colon (':') as %3A we @@ -183,10 +192,11 @@ class Request extends Message { final pathSegments = '$handlerPart$join$rest'; if (pathSegments != requestedUri.pathSegments.join('/')) { throw ArgumentError.value( - requestedUri, - 'requestedUri', - 'handlerPath "${this.handlerPath}" and url "${this.url}" must ' - 'combine to equal requestedUri path "${requestedUri.path}".'); + requestedUri, + 'requestedUri', + 'handlerPath "${this.handlerPath}" and url "${this.url}" must ' + 'combine to equal requestedUri path "${requestedUri.path}".', + ); } } @@ -245,18 +255,24 @@ Uri _computeUrl(final Uri requestedUri, String? handlerPath, final Uri? url) { if (url != null) { if (url.scheme.isNotEmpty || url.hasAuthority || url.fragment.isNotEmpty) { - throw ArgumentError('url "$url" may contain only a path and query ' - 'parameters.'); + throw ArgumentError( + 'url "$url" may contain only a path and query ' + 'parameters.', + ); } if (!requestedUri.path.endsWith(url.path)) { - throw ArgumentError('url "$url" must be a suffix of requestedUri ' - '"$requestedUri".'); + throw ArgumentError( + 'url "$url" must be a suffix of requestedUri ' + '"$requestedUri".', + ); } if (requestedUri.query != url.query) { - throw ArgumentError('url "$url" must have the same query parameters ' - 'as requestedUri "$requestedUri".'); + throw ArgumentError( + 'url "$url" must have the same query parameters ' + 'as requestedUri "$requestedUri".', + ); } if (url.path.startsWith('/')) { @@ -266,15 +282,18 @@ Uri _computeUrl(final Uri requestedUri, String? handlerPath, final Uri? url) { final startOfUrl = requestedUri.path.length - url.path.length; if (url.path.isNotEmpty && requestedUri.path.substring(startOfUrl - 1, startOfUrl) != '/') { - throw ArgumentError('url "$url" must be on a path boundary in ' - 'requestedUri "$requestedUri".'); + throw ArgumentError( + 'url "$url" must be on a path boundary in ' + 'requestedUri "$requestedUri".', + ); } return url; } else if (handlerPath != null) { return Uri( - path: requestedUri.path.substring(handlerPath.length), - query: requestedUri.query); + path: requestedUri.path.substring(handlerPath.length), + query: requestedUri.query, + ); } else { // Skip the initial "/". final path = requestedUri.path.substring(1); @@ -287,7 +306,10 @@ Uri _computeUrl(final Uri requestedUri, String? handlerPath, final Uri? url) { /// If [handlerPath] is `null`, the value is inferred from [requestedUri] and /// [url] if available. Otherwise [handlerPath] is returned. String _computeHandlerPath( - final Uri requestedUri, String? handlerPath, final Uri? url) { + final Uri requestedUri, + String? handlerPath, + final Uri? url, +) { if (handlerPath != null && handlerPath != requestedUri.path && !handlerPath.endsWith('/')) { @@ -296,8 +318,10 @@ String _computeHandlerPath( if (handlerPath != null) { if (!requestedUri.path.startsWith(handlerPath)) { - throw ArgumentError('handlerPath "$handlerPath" must be a prefix of ' - 'requestedUri path "${requestedUri.path}"'); + throw ArgumentError( + 'handlerPath "$handlerPath" must be a prefix of ' + 'requestedUri path "${requestedUri.path}"', + ); } if (!handlerPath.startsWith('/')) { diff --git a/lib/src/message/response.dart b/lib/src/message/response.dart index 786ea2a5..3862e6b1 100644 --- a/lib/src/message/response.dart +++ b/lib/src/message/response.dart @@ -114,12 +114,12 @@ class Response extends Message { final Encoding? encoding, final Map? context, }) : this( - 200, - body: body ?? Body.empty(), - headers: headers ?? Headers.empty(), - encoding: encoding, - context: context, - ); + 200, + body: body ?? Body.empty(), + headers: headers ?? Headers.empty(), + encoding: encoding, + context: context, + ); /// Constructs a 301 Moved Permanently response. /// @@ -134,14 +134,7 @@ class Response extends Message { final Headers? headers, final Encoding? encoding, final Map? context, - }) : this._redirect( - 301, - location, - body, - headers, - encoding, - context: context, - ); + }) : this._redirect(301, location, body, headers, encoding, context: context); /// Constructs a 302 Found response. /// @@ -156,14 +149,7 @@ class Response extends Message { final Headers? headers, final Encoding? encoding, final Map? context, - }) : this._redirect( - 302, - location, - body, - headers, - encoding, - context: context, - ); + }) : this._redirect(302, location, body, headers, encoding, context: context); /// Constructs a 303 See Other response. /// @@ -190,13 +176,14 @@ class Response extends Message { final Encoding? encoding, { final Map? context, }) : this( - statusCode, - body: body ?? Body.empty(), - encoding: encoding, - headers: (headers ?? Headers.empty()) - .transform((final mh) => mh.location = location), - context: context, - ); + statusCode, + body: body ?? Body.empty(), + encoding: encoding, + headers: (headers ?? Headers.empty()).transform( + (final mh) => mh.location = location, + ), + context: context, + ); /// Constructs a 204 No Content response. /// @@ -208,11 +195,11 @@ class Response extends Message { final Headers? headers, final Map? context, }) : this( - 204, - body: Body.empty(), - headers: headers ?? Headers.empty(), - context: context, - ); + 204, + body: Body.empty(), + headers: headers ?? Headers.empty(), + context: context, + ); /// Constructs a 304 Not Modified response. /// @@ -229,11 +216,11 @@ class Response extends Message { final Headers? headers, final Map? context, }) : this( - 304, - body: Body.empty(), - context: context, - headers: headers ?? Headers.empty(), - ); + 304, + body: Body.empty(), + context: context, + headers: headers ?? Headers.empty(), + ); /// Constructs a 400 Bad Request response. /// @@ -246,12 +233,12 @@ class Response extends Message { final Encoding? encoding, final Map? context, }) : this( - 400, - headers: headers ?? Headers.empty(), - body: body ?? Body.fromString('Bad Request'), - context: context, - encoding: encoding, - ); + 400, + headers: headers ?? Headers.empty(), + body: body ?? Body.fromString('Bad Request'), + context: context, + encoding: encoding, + ); /// Constructs a 401 Unauthorized response. /// @@ -265,12 +252,12 @@ class Response extends Message { final Encoding? encoding, final Map? context, }) : this( - 401, - headers: headers ?? Headers.empty(), - body: body ?? Body.fromString('Unauthorized'), - context: context, - encoding: encoding, - ); + 401, + headers: headers ?? Headers.empty(), + body: body ?? Body.fromString('Unauthorized'), + context: context, + encoding: encoding, + ); /// Constructs a 403 Forbidden response. /// @@ -283,12 +270,12 @@ class Response extends Message { final Encoding? encoding, final Map? context, }) : this( - 403, - headers: headers ?? Headers.empty(), - body: body ?? Body.fromString('Forbidden'), - context: context, - encoding: encoding, - ); + 403, + headers: headers ?? Headers.empty(), + body: body ?? Body.fromString('Forbidden'), + context: context, + encoding: encoding, + ); /// Constructs a 404 Not Found response. /// @@ -302,12 +289,12 @@ class Response extends Message { final Encoding? encoding, final Map? context, }) : this( - 404, - headers: headers ?? Headers.empty(), - body: body ?? Body.fromString('Not Found'), - context: context, - encoding: encoding, - ); + 404, + headers: headers ?? Headers.empty(), + body: body ?? Body.fromString('Not Found'), + context: context, + encoding: encoding, + ); /// Constructs a 500 Internal Server Error response. /// @@ -321,12 +308,12 @@ class Response extends Message { final Encoding? encoding, final Map? context, }) : this( - 500, - headers: headers ?? Headers.empty(), - body: body ?? Body.fromString('Internal Server Error'), - context: context, - encoding: encoding, - ); + 500, + headers: headers ?? Headers.empty(), + body: body ?? Body.fromString('Internal Server Error'), + context: context, + encoding: encoding, + ); /// Constructs a 501 Not Implemented response. /// @@ -340,12 +327,12 @@ class Response extends Message { final Encoding? encoding, final Map? context, }) : this( - 501, - headers: headers ?? Headers.empty(), - body: body ?? Body.fromString('Not Implemented'), - context: context, - encoding: encoding, - ); + 501, + headers: headers ?? Headers.empty(), + body: body ?? Body.fromString('Not Implemented'), + context: context, + encoding: encoding, + ); /// Constructs an HTTP response with the given [statusCode]. /// @@ -358,10 +345,7 @@ class Response extends Message { final Headers? headers, final Encoding? encoding, final Map? context, - }) : super( - body: body ?? Body.empty(), - headers: headers ?? Headers.empty(), - ) { + }) : super(body: body ?? Body.empty(), headers: headers ?? Headers.empty()) { if (statusCode < 100) { throw ArgumentError('Invalid status code: $statusCode.'); } diff --git a/lib/src/middleware/context_property.dart b/lib/src/middleware/context_property.dart index 40a391de..abee6178 100644 --- a/lib/src/middleware/context_property.dart +++ b/lib/src/middleware/context_property.dart @@ -45,8 +45,9 @@ class ContextProperty { T operator [](final RequestContext requestContext) { return _storage[requestContext.token] ?? (throw StateError( - 'ContextProperty value not found. Property: ${_debugName ?? T.toString()}. ' - 'Ensure middleware has set this value for the request token.')); + 'ContextProperty value not found. Property: ${_debugName ?? T.toString()}. ' + 'Ensure middleware has set this value for the request token.', + )); } /// Retrieves the value associated with the given [requestContext], or `null` if no value is set. diff --git a/lib/src/middleware/middleware_logger.dart b/lib/src/middleware/middleware_logger.dart index 451df6a3..22232d47 100644 --- a/lib/src/middleware/middleware_logger.dart +++ b/lib/src/middleware/middleware_logger.dart @@ -11,37 +11,33 @@ import '../logger/logger.dart'; /// The `isError` parameter indicates whether the message is caused by an error. /// /// If [logger] is not passed, the message is just passed to [print]. -Middleware logRequests({ - final Logger? logger, -}) => - (final innerHandler) { - final localLogger = logger ?? logMessage; +Middleware logRequests({final Logger? logger}) => (final innerHandler) { + final localLogger = logger ?? logMessage; - return (final ctx) async { - final startTime = DateTime.now(); - final watch = Stopwatch()..start(); + return (final ctx) async { + final startTime = DateTime.now(); + final watch = Stopwatch()..start(); - try { - final handledCtx = await innerHandler(ctx); - final msg = switch (handledCtx) { - final ResponseContext rc => '${rc.response.statusCode}', - final HijackContext _ => 'hijacked', - final ConnectContext _ => 'connected', - }; - localLogger( - _message(startTime, handledCtx.request, watch.elapsed, msg)); - return handledCtx; - } catch (error, stackTrace) { - localLogger( - _errorMessage(startTime, ctx.request, watch.elapsed, error), - type: LoggerType.error, - stackTrace: stackTrace, - ); - - rethrow; - } + try { + final handledCtx = await innerHandler(ctx); + final msg = switch (handledCtx) { + final ResponseContext rc => '${rc.response.statusCode}', + final HijackContext _ => 'hijacked', + final ConnectContext _ => 'connected', }; - }; + localLogger(_message(startTime, handledCtx.request, watch.elapsed, msg)); + return handledCtx; + } catch (error, stackTrace) { + localLogger( + _errorMessage(startTime, ctx.request, watch.elapsed, error), + type: LoggerType.error, + stackTrace: stackTrace, + ); + + rethrow; + } + }; +}; String _formatQuery(final String query) { return query == '' ? '' : '?$query'; diff --git a/lib/src/middleware/routing_middleware.dart b/lib/src/middleware/routing_middleware.dart index 335f4a97..8a79a556 100644 --- a/lib/src/middleware/routing_middleware.dart +++ b/lib/src/middleware/routing_middleware.dart @@ -3,12 +3,14 @@ import '../../relic.dart'; import '../router/normalized_path.dart'; import '../router/path_trie.dart'; -final _routingContext = ContextProperty< - ({ - Parameters parameters, - NormalizedPath matched, - NormalizedPath remaining - })>(); +final _routingContext = + ContextProperty< + ({ + Parameters parameters, + NormalizedPath matched, + NormalizedPath remaining, + }) + >(); /// Creates middleware that routes requests using the provided [router]. /// @@ -57,8 +59,7 @@ final _routingContext = ContextProperty< Middleware routeWith( final Router router, { final Handler Function(T)? toHandler, -}) => - _RoutingMiddlewareBuilder(router, toHandler: toHandler).asMiddleware; +}) => _RoutingMiddlewareBuilder(router, toHandler: toHandler).asMiddleware; bool _isSubtype() => [] is List; @@ -87,8 +88,12 @@ class _RoutingMiddlewareBuilder { final result = _router.lookup(req.method, path); switch (result) { case MethodMiss(): - return ctx.respond(Response(405, - headers: Headers.build((final mh) => mh.allow = result.allowed))); + return ctx.respond( + Response( + 405, + headers: Headers.build((final mh) => mh.allow = result.allowed), + ), + ); case PathMiss(): return await next(ctx); case final RouterMatch match: diff --git a/lib/src/relic_server.dart b/lib/src/relic_server.dart index a811970f..7d52735c 100644 --- a/lib/src/relic_server.dart +++ b/lib/src/relic_server.dart @@ -32,7 +32,8 @@ sealed class RelicServer { final int noOfIsolates = 1, }) { return switch (noOfIsolates) { - < 1 => throw RangeError.value( + < 1 => + throw RangeError.value( noOfIsolates, 'noOfIsolates', 'Must be larger than 0', @@ -51,7 +52,7 @@ final class _RelicServer implements RelicServer { /// Creates a server with the given parameters. _RelicServer(final Factory adapterFactory) - : _adapter = adapterFactory(); + : _adapter = adapterFactory(); /// Mounts a handler to the server and starts listening for requests. /// @@ -80,15 +81,18 @@ final class _RelicServer implements RelicServer { /// Starts listening for requests. Future _startListening() async { final adapter = await _adapter; - catchTopLevelErrors(() { - _subscription = adapter.requests.listen(_handleRequest); - }, (final error, final stackTrace) { - logMessage( - 'Asynchronous error\n$error', - stackTrace: stackTrace, - type: LoggerType.error, - ); - }); + catchTopLevelErrors( + () { + _subscription = adapter.requests.listen(_handleRequest); + }, + (final error, final stackTrace) { + logMessage( + 'Asynchronous error\n$error', + stackTrace: stackTrace, + type: LoggerType.error, + ); + }, + ); } Future _handleRequest(final AdapterRequest adapterRequest) async { @@ -112,12 +116,15 @@ final class _RelicServer implements RelicServer { } try { - final ctx = request - .toContext(adapterRequest); // adapter request will be the token + final ctx = request.toContext( + adapterRequest, + ); // adapter request will be the token final newCtx = await handler(ctx); return switch (newCtx) { - final ResponseContext rc => - adapter.respond(adapterRequest, rc.response), + final ResponseContext rc => adapter.respond( + adapterRequest, + rc.response, + ), final HijackContext hc => adapter.hijack(adapterRequest, hc.callback), final ConnectContext cc => adapter.connect(adapterRequest, cc.callback), }; @@ -139,14 +146,14 @@ final class _RelicServer implements RelicServer { final handledCtx = await handler(ctx); return switch (handledCtx) { final ResponseContext rc => - // If the response doesn't have a date header, add the default one - rc.respond( - rc.response.copyWith(headers: rc.response.headers.transform( - (final mh) { - mh.date ??= DateTime.now(); - }, - )), + // If the response doesn't have a date header, add the default one + rc.respond( + rc.response.copyWith( + headers: rc.response.headers.transform((final mh) { + mh.date ??= DateTime.now(); + }), ), + ), _ => handledCtx, }; } on HeaderException catch (error, stackTrace) { @@ -156,16 +163,19 @@ final class _RelicServer implements RelicServer { 'Error parsing request headers.\n$error', stackTrace, ); - return ctx.respond(Response.badRequest( - body: Body.fromString(error.httpResponseBody), - )); + return ctx.respond( + Response.badRequest(body: Body.fromString(error.httpResponseBody)), + ); } }; } } void _logError( - final Request request, final String message, final StackTrace stackTrace) { + final Request request, + final String message, + final StackTrace stackTrace, +) { final buffer = StringBuffer(); buffer.write('${request.method} ${request.requestedUri.path}'); if (request.requestedUri.query.isNotEmpty) { @@ -174,17 +184,13 @@ void _logError( buffer.writeln(); buffer.write(message); - logMessage( - buffer.toString(), - stackTrace: stackTrace, - type: LoggerType.error, - ); + logMessage(buffer.toString(), stackTrace: stackTrace, type: LoggerType.error); } final class _IsolatedRelicServer extends IsolatedObject implements RelicServer { _IsolatedRelicServer(final Factory adapterFactory) - : super(() => RelicServer(adapterFactory)); + : super(() => RelicServer(adapterFactory)); @override Future close() async { @@ -209,7 +215,9 @@ final class _MultiIsolateRelicServer implements RelicServer { final Factory adapterFactory, final int noOfIsolates, ) : _children = List.generate( - noOfIsolates, (final _) => _IsolatedRelicServer(adapterFactory)); + noOfIsolates, + (_) => _IsolatedRelicServer(adapterFactory), + ); @override Future close() async { diff --git a/lib/src/router/method.dart b/lib/src/router/method.dart index 9cff4444..d1ea3e07 100644 --- a/lib/src/router/method.dart +++ b/lib/src/router/method.dart @@ -11,7 +11,7 @@ enum Method { connect; static final _reverseMap = { - for (final r in values) r.name: r + for (final r in values) r.name: r, }; /// Parses a [method] string and returns the corresponding [Method] instance. diff --git a/lib/src/router/path_trie.dart b/lib/src/router/path_trie.dart index 2ba5ac60..0eccd7b7 100644 --- a/lib/src/router/path_trie.dart +++ b/lib/src/router/path_trie.dart @@ -84,11 +84,12 @@ final class PathTrie { // Mark the end node and handle potential overwrites if (currentNode.value != null) { throw ArgumentError.value( - normalizedPath, - 'normalizedPath', - 'Value already registered: ' - 'Existing: "${currentNode.value}" ' - 'New: "$value"'); + normalizedPath, + 'normalizedPath', + 'Value already registered: ' + 'Existing: "${currentNode.value}" ' + 'New: "$value"', + ); } currentNode.value = value; } @@ -151,7 +152,10 @@ final class PathTrie { final currentNode = _find(normalizedPath); if (currentNode == null || currentNode.value == null) { throw ArgumentError.value( - normalizedPath, 'normalizedPath', 'No value registered'); + normalizedPath, + 'normalizedPath', + 'No value registered', + ); } currentNode.value = value; } @@ -194,7 +198,8 @@ final class PathTrie { // Helper function @pragma('vm:prefer-inline') _TrieNode? nextIf>( - final _DynamicSegment? dynamicSegment) { + final _DynamicSegment? dynamicSegment, + ) { if (dynamicSegment != null && dynamicSegment is U) { return dynamicSegment.node; } @@ -233,13 +238,16 @@ final class PathTrie { // Helper function @pragma('vm:prefer-inline') void isA>( - final _DynamicSegment? dynamicSegment, final int segmentNo) { + final _DynamicSegment? dynamicSegment, + final int segmentNo, + ) { if (dynamicSegment != null && dynamicSegment is! U) { normalizedPath.raiseInvalidSegment( - segmentNo, - 'Conflicting segment type at the same level: ' - 'Existing: ${dynamicSegment.runtimeType}, ' - 'New: $U'); + segmentNo, + 'Conflicting segment type at the same level: ' + 'Existing: ${dynamicSegment.runtimeType}, ' + 'New: $U', + ); } } @@ -253,8 +261,10 @@ final class PathTrie { normalizedPath.raiseInvalidSegment(i, 'Starts with "**"'); } if (i < segments.length - 1) { - normalizedPath.raiseInvalidSegment(i, - 'Tail segment (**) must be the last segment in the path definition.'); + normalizedPath.raiseInvalidSegment( + i, + 'Tail segment (**) must be the last segment in the path definition.', + ); } isA<_Tail>(dynamicSegment, i); currentNode = (currentNode.dynamicSegment ??= _Tail()).node; @@ -271,7 +281,9 @@ final class PathTrie { final paramName = segment.substring(1).trim(); if (paramName.isEmpty) { normalizedPath.raiseInvalidSegment( - i, 'Parameter name cannot be empty'); + i, + 'Parameter name cannot be empty', + ); } // Ensure parameter child exists and handle name conflicts var parameter = dynamicSegment as _Parameter?; @@ -361,9 +373,12 @@ final class PathTrie { void updateMap() { final cm = currentMap; final m = currentNode.map; - currentMap = cm == null - ? m // may also be null - : (m == null ? cm : (final v) => cm(m(v))); // compose map function + currentMap = + cm == null + ? m // may also be null + : (m == null + ? cm + : (final v) => cm(m(v))); // compose map function } int i = 0; @@ -411,9 +426,15 @@ final class PathTrie { } extension on NormalizedPath { - Never raiseInvalidSegment(final int segmentNo, final String message, - {final String name = 'normalizedPath'}) { - throw ArgumentError.value(this, name, - 'Segment no $segmentNo: "${segments[segmentNo]}" is invalid. $message'); + Never raiseInvalidSegment( + final int segmentNo, + final String message, { + final String name = 'normalizedPath', + }) { + throw ArgumentError.value( + this, + name, + 'Segment no $segmentNo: "${segments[segmentNo]}" is invalid. $message', + ); } } diff --git a/lib/src/router/relic_app.dart b/lib/src/router/relic_app.dart index af76ca35..f356a7c9 100644 --- a/lib/src/router/relic_app.dart +++ b/lib/src/router/relic_app.dart @@ -132,8 +132,9 @@ class _HotReloader { const streamId = vm.EventStreams.kIsolate; return vmService.onIsolateEvent .asBroadcastStream( - onListen: (final _) => vmService.streamListen(streamId), - onCancel: (final _) => vmService.streamCancel(streamId)) + onListen: (_) => vmService.streamListen(streamId), + onCancel: (_) => vmService.streamCancel(streamId), + ) .where((final e) => e.kind == vm.EventKind.kIsolateReload); } return null; // no vm service available @@ -142,7 +143,7 @@ class _HotReloader { Future register(final RelicApp app) async { final reloadStream = await _reloadStream; if (reloadStream != null) { - return reloadStream.asyncListen((final _) => app._reload()); + return reloadStream.asyncListen((_) => app._reload()); } return null; } diff --git a/lib/src/router/router.dart b/lib/src/router/router.dart index df743a87..3a776da8 100644 --- a/lib/src/router/router.dart +++ b/lib/src/router/router.dart @@ -16,8 +16,11 @@ part 'relic_app.dart'; extension type _RouterEntry._(List _routeByVerb) implements Iterable { _RouterEntry() - : _routeByVerb = - List.filled(Method.values.length, null, growable: false); + : _routeByVerb = List.filled( + Method.values.length, + null, + growable: false, + ); @pragma('vm:prefer-inline') void add( @@ -115,11 +118,11 @@ final class Router { /// ``` void use(final String path, final T Function(T) map) { _allRoutes.use( - NormalizedPath(path), - (final r) => _RouterEntry._(List.of( - r.map((final v) => v == null ? null : map(v)), - growable: false, - ))); + NormalizedPath(path), + (final r) => _RouterEntry._( + List.of(r.map((final v) => v == null ? null : map(v)), growable: false), + ), + ); } /// Attaches a sub-router to this router at the specified [path]. @@ -151,12 +154,7 @@ final class Router { final route = entry.value.find(method); if (route == null) return MethodMiss(entry.value.allowed); - return RouterMatch( - route, - entry.parameters, - entry.matched, - entry.remaining, - ); + return RouterMatch(route, entry.parameters, entry.matched, entry.remaining); } /// Returns true if the router has no routes. diff --git a/lib/src/router/router_handler_extension.dart b/lib/src/router/router_handler_extension.dart index ea9b8aeb..db9f0e27 100644 --- a/lib/src/router/router_handler_extension.dart +++ b/lib/src/router/router_handler_extension.dart @@ -24,7 +24,7 @@ extension RouterHandlerEx on RelicRouter { /// Similar to [HandlerObject] this extension allows a [Router] /// to be callable like a [Handler]. - FutureOr call(final NewContext ctx) => - const Pipeline().addMiddleware(routeWith(this)).addHandler( - fallback ?? respondWith((final _) => Response.notFound()))(ctx); + FutureOr call(final NewContext ctx) => const Pipeline() + .addMiddleware(routeWith(this)) + .addHandler(fallback ?? respondWith((_) => Response.notFound()))(ctx); } diff --git a/lib/src/util/util.dart b/lib/src/util/util.dart index ddc08be2..971cad28 100644 --- a/lib/src/util/util.dart +++ b/lib/src/util/util.dart @@ -5,8 +5,10 @@ import 'dart:async'; /// If `this` is called in a non-root error zone, it will just run [callback] /// and return the result. Otherwise, it will capture any errors using /// [runZoned] and pass them to [onError]. -void catchTopLevelErrors(final void Function() callback, - final void Function(dynamic error, StackTrace) onError) { +void catchTopLevelErrors( + final void Function() callback, + final void Function(dynamic error, StackTrace) onError, +) { if (Zone.current.inSameErrorZone(Zone.root)) { return runZonedGuarded(callback, onError); } else { @@ -29,11 +31,7 @@ extension EventSinkEx on EventSink { /// to this sink. StreamSink mapFrom(final T Function(R) mapper) { final controller = StreamController(); - controller.stream.map(mapper).listen( - add, - onError: addError, - onDone: close, - ); + controller.stream.map(mapper).listen(add, onError: addError, onDone: close); return controller.sink; } } @@ -48,7 +46,10 @@ extension StreamEx on Stream { final Function? onError, final void Function()? onDone, final bool? cancelOnError, - }) => - asyncMap(onData).listen((final _) {}, - onError: onError, onDone: onDone, cancelOnError: cancelOnError); + }) => asyncMap(onData).listen( + (_) {}, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); } diff --git a/test/body/body_infer_mime_type_test.dart b/test/body/body_infer_mime_type_test.dart index 5ce9e72b..f08e6d5e 100644 --- a/test/body/body_infer_mime_type_test.dart +++ b/test/body/body_infer_mime_type_test.dart @@ -6,8 +6,7 @@ import 'package:relic/src/body/types/mime_type.dart'; import 'package:test/test.dart'; void main() { - test( - 'Given JSON content without explicit mimeType, ' + test('Given JSON content without explicit mimeType, ' 'when Body.fromString is called, ' 'then it infers application/json', () { const jsonContent = '{"key": "value"}'; @@ -15,8 +14,7 @@ void main() { expect(body.bodyType?.mimeType, MimeType.json); }); - test( - 'Given HTML content without explicit mimeType, ' + test('Given HTML content without explicit mimeType, ' 'when Body.fromString is called, ' 'then it infers text/html', () { const htmlContent = 'Hello'; @@ -24,18 +22,17 @@ void main() { expect(body.bodyType?.mimeType, MimeType.html); }); - test( - 'Given HTML content with whitespace prefix, ' + test('Given HTML content with whitespace prefix, ' 'when Body.fromString is called, ' 'then it infers text/html', () { - const htmlContentWithWhitespace = ' \t \n \r ' // some whitespace + const htmlContentWithWhitespace = + ' \t \n \r ' // some whitespace 'Hello'; // followed by html final body = Body.fromString(htmlContentWithWhitespace); expect(body.bodyType?.mimeType, MimeType.html); }); - test( - 'Given XML content without explicit mimeType, ' + test('Given XML content without explicit mimeType, ' 'when Body.fromString is called, ' 'then it infers application/xml', () { const xmlContent = ''; @@ -43,8 +40,7 @@ void main() { expect(body.bodyType?.mimeType, MimeType.xml); }); - test( - 'Given plain text content without explicit mimeType, ' + test('Given plain text content without explicit mimeType, ' 'when Body.fromString is called, ' 'then it defaults to text/plain', () { const plainTextContent = 'Just some plain text'; @@ -52,8 +48,7 @@ void main() { expect(body.bodyType?.mimeType, MimeType.plainText); }); - test( - 'Given empty string without explicit mimeType, ' + test('Given empty string without explicit mimeType, ' 'when Body.fromString is called, ' 'then it defaults to text/plain', () { const emptyContent = ''; @@ -61,32 +56,23 @@ void main() { expect(body.bodyType?.mimeType, MimeType.plainText); }); - test( - 'Given JSON content with explicit mimeType, ' + test('Given JSON content with explicit mimeType, ' 'when Body.fromString is called, ' 'then it uses the explicit mimeType', () { const jsonContent = '{"key": "value"}'; - final body = Body.fromString( - jsonContent, - mimeType: MimeType.plainText, - ); + final body = Body.fromString(jsonContent, mimeType: MimeType.plainText); expect(body.bodyType?.mimeType, MimeType.plainText); }); - test( - 'Given content with custom encoding, ' + test('Given content with custom encoding, ' 'when Body.fromString is called, ' 'then it preserves the encoding', () { const content = 'Héllo world'; - final body = Body.fromString( - content, - encoding: latin1, - ); + final body = Body.fromString(content, encoding: latin1); expect(body.bodyType?.encoding, latin1); }); - test( - 'Given inferred MIME type, ' + test('Given inferred MIME type, ' 'when Body.fromString is called without encoding, ' 'then it uses utf8 encoding by default', () { const jsonContent = '{"key": "value"}'; @@ -94,8 +80,7 @@ void main() { expect(body.bodyType?.encoding, utf8); }); - test( - 'Given PNG binary data without explicit mimeType, ' + test('Given PNG binary data without explicit mimeType, ' 'when Body.fromData is called, ' 'then it infers image/png', () { final pngBytes = Uint8List.fromList([ @@ -106,8 +91,7 @@ void main() { expect(body.bodyType?.mimeType.subType, 'png'); }); - test( - 'Given JPEG binary data without explicit mimeType, ' + test('Given JPEG binary data without explicit mimeType, ' 'when Body.fromData is called, ' 'then it infers image/jpeg', () { final jpegBytes = Uint8List.fromList([ @@ -118,8 +102,7 @@ void main() { expect(body.bodyType?.mimeType.subType, 'jpeg'); }); - test( - 'Given GIF binary data without explicit mimeType, ' + test('Given GIF binary data without explicit mimeType, ' 'when Body.fromData is called, ' 'then it infers image/gif', () { final gifBytes = Uint8List.fromList(utf8.encode('GIF89a')); @@ -128,8 +111,7 @@ void main() { expect(body.bodyType?.mimeType.subType, 'gif'); }); - test( - 'Given PDF binary data without explicit mimeType, ' + test('Given PDF binary data without explicit mimeType, ' 'when Body.fromData is called, ' 'then it infers application/pdf', () { final pdfBytes = Uint8List.fromList(utf8.encode('%PDF-1.4')); @@ -137,8 +119,7 @@ void main() { expect(body.bodyType?.mimeType, MimeType.pdf); }); - test( - 'Given unrecognizable binary data without explicit mimeType, ' + test('Given unrecognizable binary data without explicit mimeType, ' 'when Body.fromData is called, ' 'then it defaults to application/octet-stream', () { final unknownBytes = Uint8List.fromList([0x00, 0x01, 0x02, 0x03]); @@ -146,8 +127,7 @@ void main() { expect(body.bodyType?.mimeType, MimeType.octetStream); }); - test( - 'Given binary data with explicit mimeType, ' + test('Given binary data with explicit mimeType, ' 'when Body.fromData is called, ' 'then it uses the explicit mimeType', () { final pngBytes = Uint8List.fromList( @@ -157,8 +137,7 @@ void main() { expect(body.bodyType?.mimeType, MimeType.json); }); - test( - 'Given empty binary data without explicit mimeType, ' + test('Given empty binary data without explicit mimeType, ' 'when Body.fromData is called, ' 'then it defaults to application/octet-stream', () { final emptyBytes = Uint8List(0); diff --git a/test/body/types/body_type_test.dart b/test/body/types/body_type_test.dart index b1017c0f..7c568c7b 100644 --- a/test/body/types/body_type_test.dart +++ b/test/body/types/body_type_test.dart @@ -7,8 +7,7 @@ import 'package:test/test.dart'; void main() { group('BodyType', () { group('toHeaderValue', () { - test( - 'Given a BodyType with only a mimeType, ' + test('Given a BodyType with only a mimeType, ' 'when toHeaderValue is called, ' 'then it returns the mimeType string', () { // Arrange @@ -21,8 +20,7 @@ void main() { expect(headerValue, 'application/json'); }); - test( - 'Given a BodyType with a mimeType and an encoding, ' + test('Given a BodyType with a mimeType and an encoding, ' 'when toHeaderValue is called, ' 'then it returns the mimeType and charset string', () { // Arrange diff --git a/test/exception/relic_exceptions_test.dart b/test/exception/relic_exceptions_test.dart index afb07bdb..28c7d104 100644 --- a/test/exception/relic_exceptions_test.dart +++ b/test/exception/relic_exceptions_test.dart @@ -22,45 +22,40 @@ void main() { }); group('Given a server', () { - test( - 'when a handler throws an InvalidHeaderException ' + test('when a handler throws an InvalidHeaderException ' 'then it returns a 400 Bad Request response with exception message ' 'included in the response body', () async { await _scheduleServer( - (final _) => throw const InvalidHeaderException( - 'Value cannot be empty', - headerType: 'test', - ), + (_) => + throw const InvalidHeaderException( + 'Value cannot be empty', + headerType: 'test', + ), ); final response = await _get(); expect(response.statusCode, 400); expect(response.body, "Invalid 'test' header: Value cannot be empty"); }); - test( - 'when a handler throws an UnimplementedError ' + test('when a handler throws an UnimplementedError ' 'then it returns a 500 Internal Server Error response', () async { - await _scheduleServer( - (final _) => throw UnimplementedError(), - ); + await _scheduleServer((_) => throw UnimplementedError()); final response = await _get(); expect(response.statusCode, 500); expect(response.body, 'Internal Server Error'); }); - test( - 'when a handler throws an Exception ' + test('when a handler throws an Exception ' 'then it returns a 500 Internal Server Error response', () async { - await _scheduleServer((final _) => throw Exception()); + await _scheduleServer((_) => throw Exception()); final response = await _get(); expect(response.statusCode, 500); expect(response.body, 'Internal Server Error'); }); - test( - 'when a handler throws an Error ' + test('when a handler throws an Error ' 'then it returns a 500 Internal Server Error response', () async { - await _scheduleServer((final _) => throw Error()); + await _scheduleServer((_) => throw Error()); final response = await _get(); expect(response.statusCode, 500); expect(response.body, 'Internal Server Error'); @@ -87,6 +82,7 @@ Future _get({ if (headers != null) request.headers.addAll(headers); final response = await request.send(); - return await http.Response.fromStream(response) - .timeout(const Duration(seconds: 1)); + return await http.Response.fromStream( + response, + ).timeout(const Duration(seconds: 1)); } diff --git a/test/handler/cascade_test.dart b/test/handler/cascade_test.dart index 9fd762c3..335099da 100644 --- a/test/handler/cascade_test.dart +++ b/test/handler/cascade_test.dart @@ -7,170 +7,229 @@ void main() { group('Given a cascade with several handlers', () { late Handler handler; setUp(() { - handler = Cascade().add(respondWith((final request) { - if (request.headers['one']?.first == 'false') { - return Response.notFound(body: Body.fromString('handler 1')); - } else { - return Response.ok(body: Body.fromString('handler 1')); - } - })).add(respondWith((final request) { - if (request.headers['two']?.first == 'false') { - return Response.notFound(body: Body.fromString('handler 2')); - } else { - return Response.ok(body: Body.fromString('handler 2')); - } - })).add(respondWith((final request) { - if (request.headers['three']?.first == 'false') { - return Response.notFound(body: Body.fromString('handler 3')); - } else { - return Response.ok(body: Body.fromString('handler 3')); - } - })).handler; + handler = + Cascade() + .add( + respondWith((final request) { + if (request.headers['one']?.first == 'false') { + return Response.notFound( + body: Body.fromString('handler 1'), + ); + } else { + return Response.ok(body: Body.fromString('handler 1')); + } + }), + ) + .add( + respondWith((final request) { + if (request.headers['two']?.first == 'false') { + return Response.notFound( + body: Body.fromString('handler 2'), + ); + } else { + return Response.ok(body: Body.fromString('handler 2')); + } + }), + ) + .add( + respondWith((final request) { + if (request.headers['three']?.first == 'false') { + return Response.notFound( + body: Body.fromString('handler 3'), + ); + } else { + return Response.ok(body: Body.fromString('handler 3')); + } + }), + ) + .handler; }); test( - 'when a request with no headers is processed then the first response should be returned', - () async { - final response = await makeSimpleRequest(handler); - expect(response.statusCode, equals(200)); - expect(response.readAsString(), completion(equals('handler 1'))); - }); + 'when a request with no headers is processed then the first response should be returned', + () async { + final response = await makeSimpleRequest(handler); + expect(response.statusCode, equals(200)); + expect(response.readAsString(), completion(equals('handler 1'))); + }, + ); test( - 'when a request with header "one: false" is processed then the second response should be returned', - () async { - final response = await makeSimpleRequest( - handler, - Request( - Method.get, - localhostUri, - headers: Headers.build((final mh) => mh['one'] = ['false']), - ), - ); - expect(response.statusCode, equals(200)); - expect(response.readAsString(), completion(equals('handler 2'))); - }); + 'when a request with header "one: false" is processed then the second response should be returned', + () async { + final response = await makeSimpleRequest( + handler, + Request( + Method.get, + localhostUri, + headers: Headers.build((final mh) => mh['one'] = ['false']), + ), + ); + expect(response.statusCode, equals(200)); + expect(response.readAsString(), completion(equals('handler 2'))); + }, + ); test( - 'when a request with headers "one: false" and "two: false" is processed then the third response should be returned', - () async { - final response = await makeSimpleRequest( - handler, - Request( - Method.get, - localhostUri, - headers: Headers.build((final mh) { - mh['one'] = ['false']; - mh['two'] = ['false']; - }), - ), - ); - - expect(response.statusCode, equals(200)); - expect(response.readAsString(), completion(equals('handler 3'))); - }); + 'when a request with headers "one: false" and "two: false" is processed then the third response should be returned', + () async { + final response = await makeSimpleRequest( + handler, + Request( + Method.get, + localhostUri, + headers: Headers.build((final mh) { + mh['one'] = ['false']; + mh['two'] = ['false']; + }), + ), + ); + + expect(response.statusCode, equals(200)); + expect(response.readAsString(), completion(equals('handler 3'))); + }, + ); test( - 'when a request with headers "one: false", "two: false", and "three: false" is processed then a 404 response should be returned', - () async { - final response = await makeSimpleRequest( - handler, - Request( - Method.get, - localhostUri, - headers: Headers.build((final mh) { - mh['one'] = ['false']; - mh['two'] = ['false']; - mh['three'] = ['false']; - }), - ), - ); - expect(response.statusCode, equals(404)); - expect(response.readAsString(), completion(equals('handler 3'))); - }); + 'when a request with headers "one: false", "two: false", and "three: false" is processed then a 404 response should be returned', + () async { + final response = await makeSimpleRequest( + handler, + Request( + Method.get, + localhostUri, + headers: Headers.build((final mh) { + mh['one'] = ['false']; + mh['two'] = ['false']; + mh['three'] = ['false']; + }), + ), + ); + expect(response.statusCode, equals(404)); + expect(response.readAsString(), completion(equals('handler 3'))); + }, + ); }); test( - 'Given a cascade with a 404 response when processed then it triggers the next handler', - () async { - final handler = Cascade() - .add(respondWith((final _) => - Response.notFound(body: Body.fromString(('handler 1'))))) - .add(respondWith( - (final _) => Response.ok(body: Body.fromString('handler 2')))) - .handler; - - final response = await makeSimpleRequest(handler); - expect(response.statusCode, equals(200)); - expect(response.readAsString(), completion(equals('handler 2'))); - }); + 'Given a cascade with a 404 response when processed then it triggers the next handler', + () async { + final handler = + Cascade() + .add( + respondWith( + (_) => + Response.notFound(body: Body.fromString(('handler 1'))), + ), + ) + .add( + respondWith( + (_) => Response.ok(body: Body.fromString('handler 2')), + ), + ) + .handler; + + final response = await makeSimpleRequest(handler); + expect(response.statusCode, equals(200)); + expect(response.readAsString(), completion(equals('handler 2'))); + }, + ); test( - 'Given a cascade with a 405 response when processed then it triggers the next handler', - () async { - final handler = Cascade() - .add(respondWith((final _) => Response(405))) - .add(respondWith( - (final _) => Response.ok(body: Body.fromString('handler 2')))) - .handler; - - final response = await makeSimpleRequest(handler); - expect(response.statusCode, equals(200)); - expect(response.readAsString(), completion(equals('handler 2'))); - }); + 'Given a cascade with a 405 response when processed then it triggers the next handler', + () async { + final handler = + Cascade() + .add(respondWith((_) => Response(405))) + .add( + respondWith( + (_) => Response.ok(body: Body.fromString('handler 2')), + ), + ) + .handler; + + final response = await makeSimpleRequest(handler); + expect(response.statusCode, equals(200)); + expect(response.readAsString(), completion(equals('handler 2'))); + }, + ); test( - 'Given a cascade with specific statusCodes when processed then it controls which statuses cause cascading', - () async { - final handler = Cascade(statusCodes: [302, 403]) - .add(respondWith((final _) => Response.found(Uri.parse('/')))) - .add(respondWith((final _) => - Response.forbidden(body: Body.fromString('handler 2')))) - .add(respondWith( - (final _) => Response.notFound(body: Body.fromString('handler 3')))) - .add(respondWith( - (final _) => Response.ok(body: Body.fromString('handler 4')))) - .handler; - - final response = await makeSimpleRequest(handler); - expect(response.statusCode, equals(404)); - expect(response.readAsString(), completion(equals('handler 3'))); - }); + 'Given a cascade with specific statusCodes when processed then it controls which statuses cause cascading', + () async { + final handler = + Cascade(statusCodes: [302, 403]) + .add(respondWith((_) => Response.found(Uri.parse('/')))) + .add( + respondWith( + (_) => Response.forbidden(body: Body.fromString('handler 2')), + ), + ) + .add( + respondWith( + (_) => Response.notFound(body: Body.fromString('handler 3')), + ), + ) + .add( + respondWith( + (_) => Response.ok(body: Body.fromString('handler 4')), + ), + ) + .handler; + + final response = await makeSimpleRequest(handler); + expect(response.statusCode, equals(404)); + expect(response.readAsString(), completion(equals('handler 3'))); + }, + ); test( - 'Given a cascade with shouldCascade when processed then it controls which responses cause cascading', - () async { - final handler = Cascade( - shouldCascade: (final response) => response.statusCode.isOdd) - .add( - respondWith((final _) => Response.movedPermanently(Uri.parse('/')))) - .add(respondWith((final _) => - Response.forbidden(body: Body.fromString('handler 2')))) - .add(respondWith( - (final _) => Response.notFound(body: Body.fromString('handler 3')))) - .add(respondWith( - (final _) => Response.ok(body: Body.fromString('handler 4')))) - .handler; - - final response = await makeSimpleRequest(handler); - expect(response.statusCode, equals(404)); - expect(response.readAsString(), completion(equals('handler 3'))); - }); + 'Given a cascade with shouldCascade when processed then it controls which responses cause cascading', + () async { + final handler = + Cascade(shouldCascade: (final response) => response.statusCode.isOdd) + .add( + respondWith((_) => Response.movedPermanently(Uri.parse('/'))), + ) + .add( + respondWith( + (_) => Response.forbidden(body: Body.fromString('handler 2')), + ), + ) + .add( + respondWith( + (_) => Response.notFound(body: Body.fromString('handler 3')), + ), + ) + .add( + respondWith( + (_) => Response.ok(body: Body.fromString('handler 4')), + ), + ) + .handler; + + final response = await makeSimpleRequest(handler); + expect(response.statusCode, equals(404)); + expect(response.readAsString(), completion(equals('handler 3'))); + }, + ); group('Given error scenarios', () { test( - 'when getting the handler for an empty cascade then it throws a StateError', - () { - expect(() => Cascade().handler, throwsStateError); - }); + 'when getting the handler for an empty cascade then it throws a StateError', + () { + expect(() => Cascade().handler, throwsStateError); + }, + ); test( - 'when both statusCodes and shouldCascade are provided then it throws an ArgumentError', - () { - expect( - () => Cascade( - statusCodes: [404, 405], shouldCascade: (final _) => false), - throwsArgumentError); - }); + 'when both statusCodes and shouldCascade are provided then it throws an ArgumentError', + () { + expect( + () => Cascade(statusCodes: [404, 405], shouldCascade: (_) => false), + throwsArgumentError, + ); + }, + ); }); } diff --git a/test/handler/pipeline_test.dart b/test/handler/pipeline_test.dart index a2fb26b3..160a7ed6 100644 --- a/test/handler/pipeline_test.dart +++ b/test/handler/pipeline_test.dart @@ -11,22 +11,22 @@ void main() { }); Handler middlewareA(final Handler innerHandler) => (final request) { - expect(accessLocation, 0); - accessLocation = 1; - final response = innerHandler(request); - expect(accessLocation, 4); - accessLocation = 5; - return response; - }; + expect(accessLocation, 0); + accessLocation = 1; + final response = innerHandler(request); + expect(accessLocation, 4); + accessLocation = 5; + return response; + }; Handler middlewareB(final Handler innerHandler) => (final request) { - expect(accessLocation, 1); - accessLocation = 2; - final response = innerHandler(request); - expect(accessLocation, 3); - accessLocation = 4; - return response; - }; + expect(accessLocation, 1); + accessLocation = 2; + final response = innerHandler(request); + expect(accessLocation, 3); + accessLocation = 4; + return response; + }; HandledContext innerHandler(final NewContext request) { expect(accessLocation, 2); @@ -35,41 +35,46 @@ void main() { } test( - 'Given a pipeline with middlewareA and middlewareB when a request is processed then it completes with accessLocation 5', - () async { - final handler = const Pipeline() - .addMiddleware(middlewareA) - .addMiddleware(middlewareB) - .addHandler(innerHandler); + 'Given a pipeline with middlewareA and middlewareB when a request is processed then it completes with accessLocation 5', + () async { + final handler = const Pipeline() + .addMiddleware(middlewareA) + .addMiddleware(middlewareB) + .addHandler(innerHandler); - final response = await makeSimpleRequest(handler); - expect(response, isNotNull); - expect(accessLocation, 5); - }); + final response = await makeSimpleRequest(handler); + expect(response, isNotNull); + expect(accessLocation, 5); + }, + ); test( - 'Given middlewareA and middlewareB when composed using extensions then a request completes with accessLocation 5', - () async { - final handler = - middlewareA.addMiddleware(middlewareB).addHandler(innerHandler); + 'Given middlewareA and middlewareB when composed using extensions then a request completes with accessLocation 5', + () async { + final handler = middlewareA + .addMiddleware(middlewareB) + .addHandler(innerHandler); - final response = await makeSimpleRequest(handler); - expect(response, isNotNull); - expect(accessLocation, 5); - }); + final response = await makeSimpleRequest(handler); + expect(response, isNotNull); + expect(accessLocation, 5); + }, + ); test( - 'Given a pipeline used as middleware when a request is processed then it completes with accessLocation 5', - () async { - final innerPipeline = - const Pipeline().addMiddleware(middlewareA).addMiddleware(middlewareB); + 'Given a pipeline used as middleware when a request is processed then it completes with accessLocation 5', + () async { + final innerPipeline = const Pipeline() + .addMiddleware(middlewareA) + .addMiddleware(middlewareB); - final handler = const Pipeline() - .addMiddleware(innerPipeline.middleware) - .addHandler(innerHandler); + final handler = const Pipeline() + .addMiddleware(innerPipeline.middleware) + .addHandler(innerHandler); - final response = await makeSimpleRequest(handler); - expect(response, isNotNull); - expect(accessLocation, 5); - }); + final response = await makeSimpleRequest(handler); + expect(response, isNotNull); + expect(accessLocation, 5); + }, + ); } diff --git a/test/headers/basic/access_control_allow_credentials_header_test.dart b/test/headers/basic/access_control_allow_credentials_header_test.dart index fbe30a31..a12e6bb0 100644 --- a/test/headers/basic/access_control_allow_credentials_header_test.dart +++ b/test/headers/basic/access_control_allow_credentials_header_test.dart @@ -21,137 +21,129 @@ void main() { tearDown(() => server.close()); - group( - 'Given an Access-Control-Allow-Credentials header with validation', - () { - test( - 'when an empty Access-Control-Allow-Credentials header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'access-control-allow-credentials': ''}, - touchHeaders: (final h) => h.accessControlAllowCredentials, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); - - test( - 'when an invalid Access-Control-Allow-Credentials header is passed then the server responds ' - 'with a bad request including a message that states the header value is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'access-control-allow-credentials': 'blabla'}, - touchHeaders: (final h) => h.accessControlAllowCredentials, + group('Given an Access-Control-Allow-Credentials header with validation', () { + test( + 'when an empty Access-Control-Allow-Credentials header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', + () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'access-control-allow-credentials': ''}, + touchHeaders: (final h) => h.accessControlAllowCredentials, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid boolean'), - ), + ), + ); + }, + ); + + test( + 'when an invalid Access-Control-Allow-Credentials header is passed then the server responds ' + 'with a bad request including a message that states the header value is invalid', + () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'access-control-allow-credentials': 'blabla'}, + touchHeaders: (final h) => h.accessControlAllowCredentials, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid boolean'), ), - ); - }, - ); + ), + ); + }, + ); - test( - 'when a Access-Control-Allow-Credentials header with a value "false" ' + test('when a Access-Control-Allow-Credentials header with a value "false" ' 'then the server responds with a bad request including a message that states the header value ' - 'must be "true" or "null"', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'access-control-allow-credentials': 'false'}, - touchHeaders: (final h) => h.accessControlAllowCredentials, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Must be true or null'), - ), - ), - ); - }, + 'must be "true" or "null"', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'access-control-allow-credentials': 'false'}, + touchHeaders: (final h) => h.accessControlAllowCredentials, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Must be true or null'), + ), + ), ); + }); - test( - 'when a Access-Control-Allow-Credentials header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'access-control-allow-credentials': 'test'}, - ); - - expect(headers, isNotNull); - }, + test( + 'when a Access-Control-Allow-Credentials header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'access-control-allow-credentials': 'test'}, + ); + + expect(headers, isNotNull); + }, + ); + + test('when a Access-Control-Allow-Credentials header with a value "true" ' + 'is passed then it should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'access-control-allow-credentials': 'true'}, + touchHeaders: (final h) => h.accessControlAllowCredentials, ); - test( - 'when a Access-Control-Allow-Credentials header with a value "true" ' - 'is passed then it should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'access-control-allow-credentials': 'true'}, - touchHeaders: (final h) => h.accessControlAllowCredentials, - ); + expect(headers.accessControlAllowCredentials, isTrue); + }); - expect(headers.accessControlAllowCredentials, isTrue); - }, - ); + test( + 'when no Access-Control-Allow-Credentials header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.accessControlAllowCredentials, + ); + + expect(headers.accessControlAllowCredentials, isNull); + }, + ); + }); - test( - 'when no Access-Control-Allow-Credentials header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.accessControlAllowCredentials, - ); + group( + 'Given an Access-Control-Allow-Credentials header without validation', + () { + group( + 'when an empty Access-Control-Allow-Credentials header is passed', + () { + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'access-control-allow-credentials': ''}, + ); - expect(headers.accessControlAllowCredentials, isNull); + final header = Headers.accessControlAllowCredentials[headers]; + expect(header.valueOrNullIfInvalid, isNull); + expect(() => header.valueOrNull, throwsInvalidHeader); + expect(() => header.value, throwsInvalidHeader); + }); }, ); }, ); - - group('Given an Access-Control-Allow-Credentials header without validation', - () { - group('when an empty Access-Control-Allow-Credentials header is passed', - () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'access-control-allow-credentials': ''}, - ); - - final header = Headers.accessControlAllowCredentials[headers]; - expect(header.valueOrNullIfInvalid, isNull); - expect(() => header.valueOrNull, throwsInvalidHeader); - expect(() => header.value, throwsInvalidHeader); - }, - ); - }); - }); } diff --git a/test/headers/basic/access_control_max_age_header_test.dart b/test/headers/basic/access_control_max_age_header_test.dart index 945a88a8..49ee012e 100644 --- a/test/headers/basic/access_control_max_age_header_test.dart +++ b/test/headers/basic/access_control_max_age_header_test.dart @@ -7,132 +7,121 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given an Access-Control-Max-Age header with validation', - () { - late RelicServer server; - - setUp(() async { - server = await createServer(); - }); - - tearDown(() => server.close()); - - test( - 'when an empty Access-Control-Max-Age header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'access-control-max-age': ''}, - touchHeaders: (final h) => h.accessControlMaxAge, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); - - test( - 'when a invalid Access-Control-Max-Age header is passed then the server ' - 'responds with a bad request including a message that states the value is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'access-control-max-age': 'invalid'}, - touchHeaders: (final h) => h.accessControlMaxAge, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid number'), - ), - ), - ); - }, - ); - - test( - 'when a Access-Control-Max-Age header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'access-control-max-age': 'test'}, - ); - - expect(headers, isNotNull); - }, - ); - - test( - 'when a Access-Control-Max-Age header is passed then it should parse the value correctly', - () async { - final headers = await getServerRequestHeaders( + group('Given an Access-Control-Max-Age header with validation', () { + late RelicServer server; + + setUp(() async { + server = await createServer(); + }); + + tearDown(() => server.close()); + + test( + 'when an empty Access-Control-Max-Age header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', + () async { + expect( + getServerRequestHeaders( server: server, - headers: {'access-control-max-age': '600'}, + headers: {'access-control-max-age': ''}, touchHeaders: (final h) => h.accessControlMaxAge, - ); - - expect(headers.accessControlMaxAge, equals(600)); - }, - ); - - test( - 'when a Access-Control-Max-Age header with extra whitespace is passed then it should parse the value correctly', - () async { - final headers = await getServerRequestHeaders( + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), + ); + }, + ); + + test( + 'when a invalid Access-Control-Max-Age header is passed then the server ' + 'responds with a bad request including a message that states the value is invalid', + () async { + expect( + getServerRequestHeaders( server: server, - headers: {'access-control-max-age': ' 600 '}, + headers: {'access-control-max-age': 'invalid'}, touchHeaders: (final h) => h.accessControlMaxAge, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid number'), + ), + ), + ); + }, + ); - expect(headers.accessControlMaxAge, equals(600)); - }, + test('when a Access-Control-Max-Age header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'access-control-max-age': 'test'}, ); - test( - 'when no Access-Control-Max-Age header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.accessControlMaxAge, - ); - - expect(headers.accessControlMaxAge, isNull); - }, - ); - }, - ); - - group( - 'Given an Access-Control-Max-Age header ' - 'when an invalid raw header is passed', - () { - final header = Headers.accessControlMaxAge[Headers.fromMap({ - 'access-control-max-age': ['invalid'] - })]; - - test( - 'then valueOrNullIfInvalid should return null', - () { - expect(header.valueOrNullIfInvalid, isNull); - expect(() => header.valueOrNull, throwsInvalidHeader); - expect(() => header.value, throwsInvalidHeader); - }, - ); - }, - ); + expect(headers, isNotNull); + }); + + test( + 'when a Access-Control-Max-Age header is passed then it should parse the value correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'access-control-max-age': '600'}, + touchHeaders: (final h) => h.accessControlMaxAge, + ); + + expect(headers.accessControlMaxAge, equals(600)); + }, + ); + + test( + 'when a Access-Control-Max-Age header with extra whitespace is passed then it should parse the value correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'access-control-max-age': ' 600 '}, + touchHeaders: (final h) => h.accessControlMaxAge, + ); + + expect(headers.accessControlMaxAge, equals(600)); + }, + ); + + test( + 'when no Access-Control-Max-Age header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.accessControlMaxAge, + ); + + expect(headers.accessControlMaxAge, isNull); + }, + ); + }); + + group('Given an Access-Control-Max-Age header ' + 'when an invalid raw header is passed', () { + final header = + Headers.accessControlMaxAge[Headers.fromMap({ + 'access-control-max-age': ['invalid'], + })]; + + test('then valueOrNullIfInvalid should return null', () { + expect(header.valueOrNullIfInvalid, isNull); + expect(() => header.valueOrNull, throwsInvalidHeader); + expect(() => header.value, throwsInvalidHeader); + }); + }); } diff --git a/test/headers/basic/access_control_request_headers_header_test.dart b/test/headers/basic/access_control_request_headers_header_test.dart index cfcc46ee..473a017c 100644 --- a/test/headers/basic/access_control_request_headers_header_test.dart +++ b/test/headers/basic/access_control_request_headers_header_test.dart @@ -7,157 +7,152 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given an Access-Control-Request-Headers header with validation', - () { - late RelicServer server; - - setUp(() async { - server = await createServer(); - }); + group('Given an Access-Control-Request-Headers header with validation', () { + late RelicServer server; - tearDown(() => server.close()); + setUp(() async { + server = await createServer(); + }); - test( - 'when an empty Access-Control-Request-Headers header is passed then the ' - 'server responds with a bad request including a message that states the ' - 'header value cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'access-control-request-headers': ''}, - touchHeaders: (final h) => h.accessControlRequestHeaders, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); + tearDown(() => server.close()); - test( - 'when an Access-Control-Request-Headers header with an empty value is ' - 'passed then the server does not respond with a bad request if the ' - 'headers is not actually used', - () async { - final headers = await getServerRequestHeaders( + test( + 'when an empty Access-Control-Request-Headers header is passed then the ' + 'server responds with a bad request including a message that states the ' + 'header value cannot be empty', + () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, headers: {'access-control-request-headers': ''}, - ); - - expect(headers, isNotNull); - }, - ); - - test( - 'when an Access-Control-Request-Headers header is passed then it ' - 'should parse the headers correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: { - 'access-control-request-headers': - 'X-Custom-Header, X-Another-Header' - }, touchHeaders: (final h) => h.accessControlRequestHeaders, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), + ); + }, + ); - expect( - headers.accessControlRequestHeaders, - equals(['X-Custom-Header', 'X-Another-Header']), - ); - }, + test('when an Access-Control-Request-Headers header with an empty value is ' + 'passed then the server does not respond with a bad request if the ' + 'headers is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'access-control-request-headers': ''}, ); - test( - 'when an Access-Control-Request-Headers header with extra whitespace is ' - 'passed then it should parse the headers correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: { - 'access-control-request-headers': - ' X-Custom-Header , X-Another-Header ' - }, - touchHeaders: (final h) => h.accessControlRequestHeaders, - ); + expect(headers, isNotNull); + }); - expect( - headers.accessControlRequestHeaders, - equals(['X-Custom-Header', 'X-Another-Header']), - ); + test('when an Access-Control-Request-Headers header is passed then it ' + 'should parse the headers correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: { + 'access-control-request-headers': 'X-Custom-Header, X-Another-Header', }, + touchHeaders: (final h) => h.accessControlRequestHeaders, ); - test( - 'when an Access-Control-Request-Headers header with duplicate headers is ' - 'passed then it should parse the headers correctly and remove duplicates', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: { - 'access-control-request-headers': - 'X-Custom-Header, X-Another-Header, X-Custom-Header' - }, - touchHeaders: (final h) => h.accessControlRequestHeaders, - ); - - expect( - headers.accessControlRequestHeaders, - equals(['X-Custom-Header', 'X-Another-Header']), - ); - }, + expect( + headers.accessControlRequestHeaders, + equals(['X-Custom-Header', 'X-Another-Header']), ); + }); - test( - 'when no Access-Control-Request-Headers header is passed then it should ' - 'default to null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.accessControlRequestHeaders, - ); - - expect(headers.accessControlRequestHeaders, isNull); - }, - ); - }, - ); + test( + 'when an Access-Control-Request-Headers header with extra whitespace is ' + 'passed then it should parse the headers correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: { + 'access-control-request-headers': + ' X-Custom-Header , X-Another-Header ', + }, + touchHeaders: (final h) => h.accessControlRequestHeaders, + ); + + expect( + headers.accessControlRequestHeaders, + equals(['X-Custom-Header', 'X-Another-Header']), + ); + }, + ); + + test( + 'when an Access-Control-Request-Headers header with duplicate headers is ' + 'passed then it should parse the headers correctly and remove duplicates', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: { + 'access-control-request-headers': + 'X-Custom-Header, X-Another-Header, X-Custom-Header', + }, + touchHeaders: (final h) => h.accessControlRequestHeaders, + ); + + expect( + headers.accessControlRequestHeaders, + equals(['X-Custom-Header', 'X-Another-Header']), + ); + }, + ); + + test( + 'when no Access-Control-Request-Headers header is passed then it should ' + 'default to null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.accessControlRequestHeaders, + ); + + expect(headers.accessControlRequestHeaders, isNull); + }, + ); + }); - group('Given an Access-Control-Request-Headers header without validation', - () { - late RelicServer server; + group( + 'Given an Access-Control-Request-Headers header without validation', + () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - group('when an empty Access-Control-Request-Headers header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'access-control-request-headers': ''}, - ); + group( + 'when an empty Access-Control-Request-Headers header is passed', + () { + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'access-control-request-headers': ''}, + ); - expect( + expect( Headers.accessControlRequestHeaders[headers].valueOrNullIfInvalid, - isNull); - expect( - () => headers.accessControlRequestHeaders, throwsInvalidHeader); + isNull, + ); + expect( + () => headers.accessControlRequestHeaders, + throwsInvalidHeader, + ); + }); }, ); - }); - }); + }, + ); } diff --git a/test/headers/basic/age_header_test.dart b/test/headers/basic/age_header_test.dart index efd76d2c..0a954dd4 100644 --- a/test/headers/basic/age_header_test.dart +++ b/test/headers/basic/age_header_test.dart @@ -7,157 +7,148 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Age /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given an Age header with validation', - () { - late RelicServer server; - - setUp(() async { - server = await createServer(); - }); - - tearDown(() => server.close()); - - test( - 'when an empty Age header is passed then the server responds with a bad ' - 'request including a message that states the header value cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'age': ''}, - touchHeaders: (final h) => h.age, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); + group('Given an Age header with validation', () { + late RelicServer server; - test( - 'when an invalid Age header is passed then the server responds with a bad ' - 'request including a message that states the age is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'age': 'invalid'}, - touchHeaders: (final h) => h.age, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid number'), - ), - ), - ); - }, - ); + setUp(() async { + server = await createServer(); + }); - test( - 'when an negative Age header is passed then the server responds with a bad ' - 'request including a message that states the age must be non-negative', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'age': '-3600'}, - touchHeaders: (final h) => h.age, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Must be non-negative'), - ), - ), - ); - }, - ); + tearDown(() => server.close()); - test( - 'when an non-integer Age header is passed then the server responds with a ' - 'bad request including a message that states the age must be an integer', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'age': '3.14'}, - touchHeaders: (final h) => h.age, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Must be an integer'), - ), + test( + 'when an empty Age header is passed then the server responds with a bad ' + 'request including a message that states the header value cannot be empty', + () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'age': ''}, + touchHeaders: (final h) => h.age, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); - - test( - 'when an Age header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + ), + ); + }, + ); + + test( + 'when an invalid Age header is passed then the server responds with a bad ' + 'request including a message that states the age is invalid', + () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, - headers: {'age': 'invalid-age-format'}, - ); - - expect(headers, isNotNull); - }, - ); - - test( - 'when a valid Age header is passed then it should parse the age correctly', - () async { - final headers = await getServerRequestHeaders( + headers: {'age': 'invalid'}, + touchHeaders: (final h) => h.age, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid number'), + ), + ), + ); + }, + ); + + test( + 'when an negative Age header is passed then the server responds with a bad ' + 'request including a message that states the age must be non-negative', + () async { + expect( + getServerRequestHeaders( server: server, - headers: {'age': '3600'}, + headers: {'age': '-3600'}, touchHeaders: (final h) => h.age, - ); - - expect(headers.age, equals(3600)); - }, - ); - - test( - 'when an Age header with extra whitespace is passed then it should parse ' - 'the age correctly', - () async { - final headers = await getServerRequestHeaders( + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Must be non-negative'), + ), + ), + ); + }, + ); + + test( + 'when an non-integer Age header is passed then the server responds with a ' + 'bad request including a message that states the age must be an integer', + () async { + expect( + getServerRequestHeaders( server: server, - headers: {'age': ' 3600 '}, + headers: {'age': '3.14'}, touchHeaders: (final h) => h.age, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Must be an integer'), + ), + ), + ); + }, + ); - expect(headers.age, equals(3600)); - }, + test('when an Age header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'age': 'invalid-age-format'}, ); - test( - 'when no Age header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.age, - ); + expect(headers, isNotNull); + }); - expect(headers.age, isNull); - }, + test( + 'when a valid Age header is passed then it should parse the age correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'age': '3600'}, + touchHeaders: (final h) => h.age, + ); + + expect(headers.age, equals(3600)); + }, + ); + + test( + 'when an Age header with extra whitespace is passed then it should parse ' + 'the age correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'age': ' 3600 '}, + touchHeaders: (final h) => h.age, + ); + + expect(headers.age, equals(3600)); + }, + ); + + test('when no Age header is passed then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.age, ); - }, - ); + + expect(headers.age, isNull); + }); + }); group('Given an Age header without validation', () { late RelicServer server; @@ -169,35 +160,29 @@ void main() { tearDown(() => server.close()); group('when an empty Age header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'age': ''}, - ); - - expect(Headers.age[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.age, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'age': ''}, + ); + + expect(Headers.age[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.age, throwsInvalidHeader); + }); }); group('when an invalid Age header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'age': 'invalid'}, - ); - - expect(Headers.age[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.age, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'age': 'invalid'}, + ); + + expect(Headers.age[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.age, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/allow_header_test.dart b/test/headers/basic/allow_header_test.dart index a322ed1b..1a254dde 100644 --- a/test/headers/basic/allow_header_test.dart +++ b/test/headers/basic/allow_header_test.dart @@ -7,127 +7,109 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Allow /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given an Allow header with validation', - () { - late RelicServer server; + group('Given an Allow header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when an empty Allow header is passed then the server responds ' + test('when an empty Allow header is passed then the server responds ' 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'allow': ''}, - touchHeaders: (final h) => h.allow, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'allow': ''}, + touchHeaders: (final h) => h.allow, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); + }); - test( - 'when an invalid method is passed then the server responds ' + test('when an invalid method is passed then the server responds ' 'with a bad request including a message that states the header value ' - 'is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'allow': 'CUSTOM'}, - touchHeaders: (final h) => h.allow, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - ), - ), - ); - }, + 'is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'allow': 'CUSTOM'}, + touchHeaders: (final h) => h.allow, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid value'), + ), + ), ); + }); - test( - 'when an Allow header with an invalid value is passed ' + test('when an Allow header with an invalid value is passed ' 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'allow': 'CUSTOM'}, - ); - - expect(headers, isNotNull); - }, + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'allow': 'CUSTOM'}, ); - test( - 'when a valid Allow header is passed then it should parse the methods correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'allow': 'GET, POST, DELETE'}, - touchHeaders: (final h) => h.allow, - ); - - expect( - headers.allow?.map((final method) => method.value).toList(), - equals(['GET', 'POST', 'DELETE']), - ); - }, + expect(headers, isNotNull); + }); + + test( + 'when a valid Allow header is passed then it should parse the methods correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'allow': 'GET, POST, DELETE'}, + touchHeaders: (final h) => h.allow, + ); + + expect( + headers.allow?.map((final method) => method.value).toList(), + equals(['GET', 'POST', 'DELETE']), + ); + }, + ); + + test('when an Allow header with duplicate methods is passed then it should ' + 'parse the methods correctly and remove duplicates', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'allow': 'GET, POST, GET'}, + touchHeaders: (final h) => h.allow, + ); + + expect( + headers.allow?.map((final method) => method.value).toList(), + equals(['GET', 'POST']), ); + }); - test( - 'when an Allow header with duplicate methods is passed then it should ' - 'parse the methods correctly and remove duplicates', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'allow': 'GET, POST, GET'}, - touchHeaders: (final h) => h.allow, - ); - - expect( - headers.allow?.map((final method) => method.value).toList(), - equals(['GET', 'POST']), - ); - }, + test('when an Allow header with spaces is passed then it should parse the ' + 'methods correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'allow': ' GET , POST , DELETE '}, + touchHeaders: (final h) => h.allow, ); - test( - 'when an Allow header with spaces is passed then it should parse the ' - 'methods correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'allow': ' GET , POST , DELETE '}, - touchHeaders: (final h) => h.allow, - ); - - expect( - headers.allow?.map((final method) => method.value).toList(), - equals(['GET', 'POST', 'DELETE']), - ); - }, + expect( + headers.allow?.map((final method) => method.value).toList(), + equals(['GET', 'POST', 'DELETE']), ); - }, - ); + }); + }); group('Given an Allow header without validation', () { late RelicServer server; @@ -139,18 +121,15 @@ void main() { tearDown(() => server.close()); group('when an empty Allow header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'allow': ''}, - ); - - expect(Headers.allow[headers].valueOrNullIfInvalid, isNull); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'allow': ''}, + ); + + expect(Headers.allow[headers].valueOrNullIfInvalid, isNull); + }); }); }); } diff --git a/test/headers/basic/content_location_header_test.dart b/test/headers/basic/content_location_header_test.dart index 50960db3..05581458 100644 --- a/test/headers/basic/content_location_header_test.dart +++ b/test/headers/basic/content_location_header_test.dart @@ -7,146 +7,134 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Location /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Content-Location header with validation', - () { - late RelicServer server; - - setUp(() async { - server = await createServer(); - }); + group('Given a Content-Location header with validation', () { + late RelicServer server; - tearDown(() => server.close()); - - test( - 'when an empty Content-Location header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'content-location': ''}, - touchHeaders: (final h) => h.contentLocation, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); + setUp(() async { + server = await createServer(); + }); - test( - 'when a Content-Location header with an invalid URI is passed then the ' - 'server responds with a bad request including a message that states the ' - 'URI is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'content-location': 'ht!tp://invalid-url'}, - touchHeaders: (final h) => h.contentLocation, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid URI format'), - ), - ), - ); - }, - ); + tearDown(() => server.close()); - test( - 'when a Content-Location header with an invalid port is passed then the ' - 'server responds with a bad request including a message that states the ' - 'URI is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'content-location': 'https://example.com:test'}, - touchHeaders: (final h) => h.contentLocation, + test( + 'when an empty Content-Location header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', + () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'content-location': ''}, + touchHeaders: (final h) => h.contentLocation, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid URI format'), - ), + ), + ); + }, + ); + + test( + 'when a Content-Location header with an invalid URI is passed then the ' + 'server responds with a bad request including a message that states the ' + 'URI is invalid', + () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'content-location': 'ht!tp://invalid-url'}, + touchHeaders: (final h) => h.contentLocation, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid URI format'), ), - ); - }, - ); - - test( - 'when a Content-Location header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + ), + ); + }, + ); + + test( + 'when a Content-Location header with an invalid port is passed then the ' + 'server responds with a bad request including a message that states the ' + 'URI is invalid', + () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, headers: {'content-location': 'https://example.com:test'}, - ); - - expect(headers, isNotNull); - }, - ); - - test( - 'when a Content-Location header with a valid URI is passed then it ' - 'should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'content-location': 'https://example.com/resource'}, touchHeaders: (final h) => h.contentLocation, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid URI format'), + ), + ), + ); + }, + ); - expect( - headers.contentLocation, - equals(Uri.parse('https://example.com/resource')), - ); - }, + test('when a Content-Location header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-location': 'https://example.com:test'}, ); - test( - 'when a Content-Location header with a valid URI and port is passed then ' - 'it should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'content-location': 'https://example.com:8080'}, - touchHeaders: (final h) => h.contentLocation, - ); + expect(headers, isNotNull); + }); - expect( - headers.contentLocation?.port, - equals(8080), - ); - }, + test('when a Content-Location header with a valid URI is passed then it ' + 'should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'content-location': 'https://example.com/resource'}, + touchHeaders: (final h) => h.contentLocation, ); - test( - 'when no Content-Location header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.contentLocation, - ); - - expect(headers.contentLocation, isNull); - }, + expect( + headers.contentLocation, + equals(Uri.parse('https://example.com/resource')), ); - }, - ); + }); + + test( + 'when a Content-Location header with a valid URI and port is passed then ' + 'it should parse correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'content-location': 'https://example.com:8080'}, + touchHeaders: (final h) => h.contentLocation, + ); + + expect(headers.contentLocation?.port, equals(8080)); + }, + ); + + test( + 'when no Content-Location header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.contentLocation, + ); + + expect(headers.contentLocation, isNull); + }, + ); + }); group('Given a Content-Location header without validation', () { late RelicServer server; @@ -158,19 +146,16 @@ void main() { tearDown(() => server.close()); group('when an invalid Content-Location header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-location': 'ht!tp://invalid-url'}, - ); - - expect(Headers.contentLocation[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.contentLocation, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-location': 'ht!tp://invalid-url'}, + ); + + expect(Headers.contentLocation[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.contentLocation, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/date_header_test.dart b/test/headers/basic/date_header_test.dart index 1a9d13bf..2c3cdc1f 100644 --- a/test/headers/basic/date_header_test.dart +++ b/test/headers/basic/date_header_test.dart @@ -8,122 +8,107 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Date header with validation', - () { - late RelicServer server; + group('Given a Date header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when an empty Date header is passed then the server responds ' + test('when an empty Date header is passed then the server responds ' 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'date': ''}, - touchHeaders: (final h) => h.date, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'date': ''}, + touchHeaders: (final h) => h.date, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); + }); - test( - 'when a Date header with an invalid date format is passed ' + test('when a Date header with an invalid date format is passed ' 'then the server responds with a bad request including a message that ' - 'states the date format is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'date': 'invalid-date-format'}, - touchHeaders: (final h) => h.date, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid date format'), - ), - ), - ); - }, + 'states the date format is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'date': 'invalid-date-format'}, + touchHeaders: (final h) => h.date, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid date format'), + ), + ), ); + }); - test( - 'when a Date header with an invalid date format is passed ' + test('when a Date header with an invalid date format is passed ' 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'date': 'invalid-date-format'}, - ); - - expect(headers, isNotNull); - }, + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'date': 'invalid-date-format'}, ); - test( - 'when a valid Date header is passed then it should parse the date correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'date': 'Wed, 21 Oct 2015 07:28:00 GMT'}, - touchHeaders: (final h) => h.date, - ); - - expect( - headers.date, - equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), - ); - }, - ); + expect(headers, isNotNull); + }); - test( - 'when a Date header with extra whitespace is passed then it should parse the date correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'date': ' Wed, 21 Oct 2015 07:28:00 GMT '}, - touchHeaders: (final h) => h.date, - ); - - expect( - headers.date, - equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), - ); - }, + test( + 'when a valid Date header is passed then it should parse the date correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'date': 'Wed, 21 Oct 2015 07:28:00 GMT'}, + touchHeaders: (final h) => h.date, + ); + + expect( + headers.date, + equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), + ); + }, + ); + + test( + 'when a Date header with extra whitespace is passed then it should parse the date correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'date': ' Wed, 21 Oct 2015 07:28:00 GMT '}, + touchHeaders: (final h) => h.date, + ); + + expect( + headers.date, + equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), + ); + }, + ); + + test('when no Date header is passed then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.date, ); - test( - 'when no Date header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.date, - ); - - expect(headers.date, isNull); - }, - ); - }, - ); + expect(headers.date, isNull); + }); + }); group('Given a Date header without validation', () { late RelicServer server; @@ -135,35 +120,29 @@ void main() { tearDown(() => server.close()); group('when an empty Date header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'date': ''}, - ); - - expect(Headers.date[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.date, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'date': ''}, + ); + + expect(Headers.date[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.date, throwsInvalidHeader); + }); }); group('when an invalid Date header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'date': 'invalid-date-format'}, - ); - - expect(Headers.date[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.date, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'date': 'invalid-date-format'}, + ); + + expect(Headers.date[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.date, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/expires_header_test.dart b/test/headers/basic/expires_header_test.dart index 27d4cf97..7a36db01 100644 --- a/test/headers/basic/expires_header_test.dart +++ b/test/headers/basic/expires_header_test.dart @@ -8,122 +8,110 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given an Expires header with validation', - () { - late RelicServer server; + group('Given an Expires header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when an empty Expires header is passed then the server responds ' + test('when an empty Expires header is passed then the server responds ' 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'expires': ''}, - touchHeaders: (final h) => h.expires, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'expires': ''}, + touchHeaders: (final h) => h.expires, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); + }); - test( - 'when an Expires header with an invalid date format is passed ' + test('when an Expires header with an invalid date format is passed ' 'then the server responds with a bad request including a message that ' - 'states the date format is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'expires': 'invalid-date-format'}, - touchHeaders: (final h) => h.expires, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid date format'), - ), - ), - ); - }, + 'states the date format is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'expires': 'invalid-date-format'}, + touchHeaders: (final h) => h.expires, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid date format'), + ), + ), ); + }); - test( - 'when an Expires header with an invalid date format is passed ' + test('when an Expires header with an invalid date format is passed ' 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'expires': 'invalid-date-format'}, - ); - - expect(headers, isNotNull); - }, - ); - - test( - 'when a valid Expires header is passed then it should parse the date correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'expires': 'Wed, 21 Oct 2015 07:28:00 GMT'}, - touchHeaders: (final h) => h.expires, - ); - - expect( - headers.expires, - equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), - ); - }, + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'expires': 'invalid-date-format'}, ); - test( - 'when an Expires header with extra whitespace is passed then it should parse the date correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'expires': ' Wed, 21 Oct 2015 07:28:00 GMT '}, - touchHeaders: (final h) => h.expires, - ); - - expect( - headers.expires, - equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), - ); - }, - ); + expect(headers, isNotNull); + }); - test( - 'when no Expires header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.expires, - ); - - expect(headers.expires, isNull); - }, - ); - }, - ); + test( + 'when a valid Expires header is passed then it should parse the date correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'expires': 'Wed, 21 Oct 2015 07:28:00 GMT'}, + touchHeaders: (final h) => h.expires, + ); + + expect( + headers.expires, + equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), + ); + }, + ); + + test( + 'when an Expires header with extra whitespace is passed then it should parse the date correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'expires': ' Wed, 21 Oct 2015 07:28:00 GMT '}, + touchHeaders: (final h) => h.expires, + ); + + expect( + headers.expires, + equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), + ); + }, + ); + + test( + 'when no Expires header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.expires, + ); + + expect(headers.expires, isNull); + }, + ); + }); group('Given an Expires header without validation', () { late RelicServer server; @@ -135,35 +123,29 @@ void main() { tearDown(() => server.close()); group('when an empty Expires header is passed ', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'expires': ''}, - ); - - expect(Headers.expires[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.expires, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'expires': ''}, + ); + + expect(Headers.expires[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.expires, throwsInvalidHeader); + }); }); group('when an invalid Expires header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'expires': 'invalid-date-format'}, - ); - - expect(Headers.expires[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.expires, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'expires': 'invalid-date-format'}, + ); + + expect(Headers.expires[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.expires, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/host_header_test.dart b/test/headers/basic/host_header_test.dart index 3731200b..0f12eb26 100644 --- a/test/headers/basic/host_header_test.dart +++ b/test/headers/basic/host_header_test.dart @@ -17,332 +17,317 @@ void main() { tearDown(() => server.close()); - group( - 'Given a request with a Host header', - () { - parameterizedTest< - ({ - String description, - String hostValue, - })>( - variants: [ - ( - description: 'when an empty Host header is passed', - hostValue: '', - ), - ( - description: 'when a Host header with an invalid format is passed', - hostValue: 'h@ttp://example.com', - ), - ( - description: - 'when a Host header with an invalid port number is passed', - hostValue: 'example.com:test', - ), - ( - description: - 'when a Host header with invalid port format (non-numeric) is passed', - hostValue: '192.168.1.1:abc', - ), - ( - description: - 'when a Host header with too big port number is passed', - hostValue: '192.168.1.1:1000000', - ), - ( - description: - 'when a Host header with a negagtive port number is passed', - hostValue: '192.168.1.1:-1', - ), - ( - description: 'when an unbracketed IPv6 address is passed', - hostValue: '2001:db8::1', - ), - ( - description: 'when a Host header with scheme (https://) is passed', - hostValue: 'https://example.com', - ), - ( - description: 'when a Host header with path component is passed', - hostValue: 'example.com/path', - ), - ( - description: 'when a Host header with query parameter is passed', - hostValue: 'example.com?q=1', - ), - ( - description: 'when a Host header with fragment is passed', - hostValue: 'example.com#frag', - ), - ( - description: 'when a Host header with user@host format is passed', - hostValue: 'user@example.com', + group('Given a request with a Host header', () { + parameterizedTest<({String description, String hostValue})>( + variants: [ + (description: 'when an empty Host header is passed', hostValue: ''), + ( + description: 'when a Host header with an invalid format is passed', + hostValue: 'h@ttp://example.com', + ), + ( + description: + 'when a Host header with an invalid port number is passed', + hostValue: 'example.com:test', + ), + ( + description: + 'when a Host header with invalid port format (non-numeric) is passed', + hostValue: '192.168.1.1:abc', + ), + ( + description: 'when a Host header with too big port number is passed', + hostValue: '192.168.1.1:1000000', + ), + ( + description: + 'when a Host header with a negagtive port number is passed', + hostValue: '192.168.1.1:-1', + ), + ( + description: 'when an unbracketed IPv6 address is passed', + hostValue: '2001:db8::1', + ), + ( + description: 'when a Host header with scheme (https://) is passed', + hostValue: 'https://example.com', + ), + ( + description: 'when a Host header with path component is passed', + hostValue: 'example.com/path', + ), + ( + description: 'when a Host header with query parameter is passed', + hostValue: 'example.com?q=1', + ), + ( + description: 'when a Host header with fragment is passed', + hostValue: 'example.com#frag', + ), + ( + description: 'when a Host header with user@host format is passed', + hostValue: 'user@example.com', + ), + (description: 'foo', hostValue: '::1'), + ], + (final testCase) => + '${testCase.description} then the server responds with a bad request', + (final testCase) async { + expect( + getServerRequestHeaders( + server: server, + headers: {'host': testCase.hostValue}, + touchHeaders: (final h) => h.host, ), - (description: 'foo', hostValue: '::1'), - ], - (final testCase) => - '${testCase.description} then the server responds with a bad request', - (final testCase) async { - expect( - getServerRequestHeaders( - server: server, - headers: {'host': testCase.hostValue}, - touchHeaders: (final h) => h.host, - ), - throwsA(isA()), - ); - }, - ); + throwsA(isA()), + ); + }, + ); - test( - 'when a Host header with an invalid value is passed ' + test('when a Host header with an invalid value is passed ' 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, // server don't use the header - headers: {'host': 'http://example.com'}, // sch eme not allowed! - ); - - expect(Headers.host[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.host, throwsInvalidHeader); - }, + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, // server don't use the header + headers: {'host': 'http://example.com'}, // sch eme not allowed! ); - // Basic host parsing tests - parameterizedTest< - ({ - String description, - String hostValue, - HostHeader expectedHost, - })>( - variants: [ - ( - description: - 'when a valid Host header is passed then it should parse the host correctly', - hostValue: 'example.com', - expectedHost: HostHeader('example.com', null), - ), - ( - description: - 'when a Host header with a port number is passed then it should parse the port number correctly', - hostValue: 'example.com:8080', - expectedHost: HostHeader('example.com', 8080), - ), - ( - description: - 'when a Host header with extra whitespace is passed then it should parse correctly', - hostValue: ' example.com ', - expectedHost: HostHeader('example.com', null), - ), - // IPv4 address tests - ( - description: - 'when a valid IPv4 address is passed then it should parse the host correctly', - hostValue: '192.168.1.1', - expectedHost: HostHeader('192.168.1.1', null), - ), - ( - description: - 'when a valid IPv4 address with port is passed then it should parse both host and port correctly', - hostValue: '192.168.1.1:8080', - expectedHost: HostHeader('192.168.1.1', 8080), - ), - ( - description: - 'when IPv4 loopback address is passed then it should parse correctly', - hostValue: '127.0.0.1', - expectedHost: HostHeader('127.0.0.1', null), - ), - ( - description: - 'when IPv4 loopback address with port is passed then it should parse both host and port correctly', - hostValue: '127.0.0.1:3000', - expectedHost: HostHeader('127.0.0.1', 3000), - ), - ( - description: - 'when IPv4 address with whitespace is passed then it should parse correctly', - hostValue: ' 10.0.0.1 ', - expectedHost: HostHeader('10.0.0.1', null), - ), - ( - description: - 'when IPv4 private network address is passed then it should parse correctly', - hostValue: '10.0.0.1:9000', - expectedHost: HostHeader('10.0.0.1', 9000), - ), - ( - description: - 'when IPv4 address with standard HTTP port is passed then it should parse correctly', - hostValue: '203.0.113.1:80', - expectedHost: HostHeader('203.0.113.1', 80), - ), - ( - description: - 'when IPv4 address with HTTPS port is passed then it should parse correctly', - hostValue: '203.0.113.1:443', - expectedHost: HostHeader('203.0.113.1', 443), - ), - ( - description: - 'when IPv4 address with high port number is passed then it should parse correctly', - hostValue: '172.16.0.1:65535', - expectedHost: HostHeader('172.16.0.1', 65535), - ), - ( - description: - 'when IPv4 broadcast address is passed then it should parse correctly', - hostValue: '255.255.255.255:8080', - expectedHost: HostHeader('255.255.255.255', 8080), - ), - ( - description: - 'when IPv4 zero address is passed then it should parse correctly', - hostValue: '0.0.0.0', - expectedHost: HostHeader('0.0.0.0', null), - ), - ( - description: - 'when IPv4 address with port 1 is passed then it should parse correctly', - hostValue: '192.168.0.1:1', - expectedHost: HostHeader('192.168.0.1', 1), - ), - ( - description: - 'when an IPv4-like address with high numbers is passed then it should parse as hostname correctly', - hostValue: '256.1.1.1', - expectedHost: HostHeader('256.1.1.1', null), - ), - ( - description: - 'when an IPv4-like address with extra octets is passed then it should parse as hostname correctly', - hostValue: '192.168.1.1.1', - expectedHost: HostHeader('192.168.1.1.1', null), - ), - ( - description: - 'when an IPv4-like address with negative numbers is passed then it should parse as hostname correctly', - hostValue: '192.168.-1.1:8080', - expectedHost: HostHeader('192.168.-1.1', 8080), - ), - // IPv6 address tests - ( - description: - 'when a valid IPv6 address in brackets is passed then it should parse the host correctly', - hostValue: '[2001:db8::1]', - expectedHost: HostHeader('[2001:db8::1]', null), - ), - ( - description: - 'when a valid IPv6 address in brackets with port is passed then it should parse both host and port correctly', - hostValue: '[2001:db8::1]:8080', - expectedHost: HostHeader('[2001:db8::1]', 8080), - ), - ( - description: - 'when an IPv6 loopback address in brackets is passed then it should parse correctly', - hostValue: '[::1]', - expectedHost: HostHeader('[::1]', null), - ), - ( - description: - 'when an IPv6 loopback address in brackets with port is passed then it should parse both host and port correctly', - hostValue: '[::1]:3000', - expectedHost: HostHeader('[::1]', 3000), - ), - ( - description: - 'when IPv6 address with whitespace is passed then it should parse correctly', - hostValue: ' [2001:db8::1] ', - expectedHost: HostHeader('[2001:db8::1]', null), - ), - ( - description: - 'when full IPv6 address in brackets is passed then it should parse correctly', - hostValue: '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', - expectedHost: - HostHeader('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', null), - ), - ( - description: - 'when IPv6 address with double colon compression is passed then it should parse correctly', - hostValue: '[2001:db8::8a2e:370:7334]:9000', - expectedHost: HostHeader('[2001:db8::8a2e:370:7334]', 9000), - ), - ( - description: - 'when IPv6 address ending with numbers that could be port is passed then it should parse correctly with brackets', - hostValue: '[2001:db8:85a3::8a2e:370:7334]:80', - expectedHost: HostHeader('[2001:db8:85a3::8a2e:370:7334]', 80), - ), - ( - description: - 'when IPv6 loopback with port-like ending is passed then it should parse correctly with brackets', - hostValue: '[::1]:8080', - expectedHost: HostHeader('[::1]', 8080), - ), - ( - description: - 'when IPv6 compressed notation that could be ambiguous is passed then it should parse correctly with brackets', - hostValue: '[::ffff:192.0.2.1]:443', - expectedHost: HostHeader('[::ffff:192.0.2.1]', 443), - ), - ( - description: - 'when IPv6 address with embedded IPv4 notation is passed then it should parse correctly with brackets', - hostValue: '[::ffff:192.168.1.1]', - expectedHost: HostHeader('[::ffff:192.168.1.1]', null), - ), - ( - description: - 'when IPv6 zero compression at end that could look like port is passed then it should parse correctly with brackets', - hostValue: '[2001:db8::]:3000', - expectedHost: HostHeader('[2001:db8::]', 3000), - ), - ( - description: - 'when IPv6 address ending exactly like common port 80 is passed then it should parse correctly with brackets', - hostValue: '[fe80::1:80]:8080', - expectedHost: HostHeader('[fe80::1:80]', 8080), - ), - ( - description: - 'when IPv6 address ending like port 443 is passed then it should parse correctly with brackets', - hostValue: '[2001:db8::443]:443', - expectedHost: HostHeader('[2001:db8::443]', 443), - ), - ( - description: - 'when minimal IPv6 with short notation is passed then it should parse correctly with brackets', - hostValue: '[::1:1]:1', - expectedHost: HostHeader('[::1:1]', 1), - ), - ( - description: - 'when IPv6 with multiple consecutive numbers is passed then it should parse correctly with brackets', - hostValue: '[2001:0:0:0:0:0:0:8080]', - expectedHost: HostHeader('[2001:0:0:0:0:0:0:8080]', null), - ), - ( - description: - 'when IPv6 with embedded IPv4 ending in port-like numbers is passed then it should parse correctly with brackets', - hostValue: '[64:ff9b::192.0.2.80]:80', - expectedHost: HostHeader('[64:ff9b::192.0.2.80]', 80), - ), - ], - (final testCase) => testCase.description, - (final testCase) async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'host': testCase.hostValue}, - touchHeaders: (final h) => h.host, - ); + expect(Headers.host[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.host, throwsInvalidHeader); + }); - expect(headers.host, equals(testCase.expectedHost)); - }, - ); - }, - ); + // Basic host parsing tests + parameterizedTest< + ({String description, String hostValue, HostHeader expectedHost}) + >( + variants: [ + ( + description: + 'when a valid Host header is passed then it should parse the host correctly', + hostValue: 'example.com', + expectedHost: HostHeader('example.com', null), + ), + ( + description: + 'when a Host header with a port number is passed then it should parse the port number correctly', + hostValue: 'example.com:8080', + expectedHost: HostHeader('example.com', 8080), + ), + ( + description: + 'when a Host header with extra whitespace is passed then it should parse correctly', + hostValue: ' example.com ', + expectedHost: HostHeader('example.com', null), + ), + // IPv4 address tests + ( + description: + 'when a valid IPv4 address is passed then it should parse the host correctly', + hostValue: '192.168.1.1', + expectedHost: HostHeader('192.168.1.1', null), + ), + ( + description: + 'when a valid IPv4 address with port is passed then it should parse both host and port correctly', + hostValue: '192.168.1.1:8080', + expectedHost: HostHeader('192.168.1.1', 8080), + ), + ( + description: + 'when IPv4 loopback address is passed then it should parse correctly', + hostValue: '127.0.0.1', + expectedHost: HostHeader('127.0.0.1', null), + ), + ( + description: + 'when IPv4 loopback address with port is passed then it should parse both host and port correctly', + hostValue: '127.0.0.1:3000', + expectedHost: HostHeader('127.0.0.1', 3000), + ), + ( + description: + 'when IPv4 address with whitespace is passed then it should parse correctly', + hostValue: ' 10.0.0.1 ', + expectedHost: HostHeader('10.0.0.1', null), + ), + ( + description: + 'when IPv4 private network address is passed then it should parse correctly', + hostValue: '10.0.0.1:9000', + expectedHost: HostHeader('10.0.0.1', 9000), + ), + ( + description: + 'when IPv4 address with standard HTTP port is passed then it should parse correctly', + hostValue: '203.0.113.1:80', + expectedHost: HostHeader('203.0.113.1', 80), + ), + ( + description: + 'when IPv4 address with HTTPS port is passed then it should parse correctly', + hostValue: '203.0.113.1:443', + expectedHost: HostHeader('203.0.113.1', 443), + ), + ( + description: + 'when IPv4 address with high port number is passed then it should parse correctly', + hostValue: '172.16.0.1:65535', + expectedHost: HostHeader('172.16.0.1', 65535), + ), + ( + description: + 'when IPv4 broadcast address is passed then it should parse correctly', + hostValue: '255.255.255.255:8080', + expectedHost: HostHeader('255.255.255.255', 8080), + ), + ( + description: + 'when IPv4 zero address is passed then it should parse correctly', + hostValue: '0.0.0.0', + expectedHost: HostHeader('0.0.0.0', null), + ), + ( + description: + 'when IPv4 address with port 1 is passed then it should parse correctly', + hostValue: '192.168.0.1:1', + expectedHost: HostHeader('192.168.0.1', 1), + ), + ( + description: + 'when an IPv4-like address with high numbers is passed then it should parse as hostname correctly', + hostValue: '256.1.1.1', + expectedHost: HostHeader('256.1.1.1', null), + ), + ( + description: + 'when an IPv4-like address with extra octets is passed then it should parse as hostname correctly', + hostValue: '192.168.1.1.1', + expectedHost: HostHeader('192.168.1.1.1', null), + ), + ( + description: + 'when an IPv4-like address with negative numbers is passed then it should parse as hostname correctly', + hostValue: '192.168.-1.1:8080', + expectedHost: HostHeader('192.168.-1.1', 8080), + ), + // IPv6 address tests + ( + description: + 'when a valid IPv6 address in brackets is passed then it should parse the host correctly', + hostValue: '[2001:db8::1]', + expectedHost: HostHeader('[2001:db8::1]', null), + ), + ( + description: + 'when a valid IPv6 address in brackets with port is passed then it should parse both host and port correctly', + hostValue: '[2001:db8::1]:8080', + expectedHost: HostHeader('[2001:db8::1]', 8080), + ), + ( + description: + 'when an IPv6 loopback address in brackets is passed then it should parse correctly', + hostValue: '[::1]', + expectedHost: HostHeader('[::1]', null), + ), + ( + description: + 'when an IPv6 loopback address in brackets with port is passed then it should parse both host and port correctly', + hostValue: '[::1]:3000', + expectedHost: HostHeader('[::1]', 3000), + ), + ( + description: + 'when IPv6 address with whitespace is passed then it should parse correctly', + hostValue: ' [2001:db8::1] ', + expectedHost: HostHeader('[2001:db8::1]', null), + ), + ( + description: + 'when full IPv6 address in brackets is passed then it should parse correctly', + hostValue: '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', + expectedHost: HostHeader( + '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', + null, + ), + ), + ( + description: + 'when IPv6 address with double colon compression is passed then it should parse correctly', + hostValue: '[2001:db8::8a2e:370:7334]:9000', + expectedHost: HostHeader('[2001:db8::8a2e:370:7334]', 9000), + ), + ( + description: + 'when IPv6 address ending with numbers that could be port is passed then it should parse correctly with brackets', + hostValue: '[2001:db8:85a3::8a2e:370:7334]:80', + expectedHost: HostHeader('[2001:db8:85a3::8a2e:370:7334]', 80), + ), + ( + description: + 'when IPv6 loopback with port-like ending is passed then it should parse correctly with brackets', + hostValue: '[::1]:8080', + expectedHost: HostHeader('[::1]', 8080), + ), + ( + description: + 'when IPv6 compressed notation that could be ambiguous is passed then it should parse correctly with brackets', + hostValue: '[::ffff:192.0.2.1]:443', + expectedHost: HostHeader('[::ffff:192.0.2.1]', 443), + ), + ( + description: + 'when IPv6 address with embedded IPv4 notation is passed then it should parse correctly with brackets', + hostValue: '[::ffff:192.168.1.1]', + expectedHost: HostHeader('[::ffff:192.168.1.1]', null), + ), + ( + description: + 'when IPv6 zero compression at end that could look like port is passed then it should parse correctly with brackets', + hostValue: '[2001:db8::]:3000', + expectedHost: HostHeader('[2001:db8::]', 3000), + ), + ( + description: + 'when IPv6 address ending exactly like common port 80 is passed then it should parse correctly with brackets', + hostValue: '[fe80::1:80]:8080', + expectedHost: HostHeader('[fe80::1:80]', 8080), + ), + ( + description: + 'when IPv6 address ending like port 443 is passed then it should parse correctly with brackets', + hostValue: '[2001:db8::443]:443', + expectedHost: HostHeader('[2001:db8::443]', 443), + ), + ( + description: + 'when minimal IPv6 with short notation is passed then it should parse correctly with brackets', + hostValue: '[::1:1]:1', + expectedHost: HostHeader('[::1:1]', 1), + ), + ( + description: + 'when IPv6 with multiple consecutive numbers is passed then it should parse correctly with brackets', + hostValue: '[2001:0:0:0:0:0:0:8080]', + expectedHost: HostHeader('[2001:0:0:0:0:0:0:8080]', null), + ), + ( + description: + 'when IPv6 with embedded IPv4 ending in port-like numbers is passed then it should parse correctly with brackets', + hostValue: '[64:ff9b::192.0.2.80]:80', + expectedHost: HostHeader('[64:ff9b::192.0.2.80]', 80), + ), + ], + (final testCase) => testCase.description, + (final testCase) async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'host': testCase.hostValue}, + touchHeaders: (final h) => h.host, + ); + + expect(headers.host, equals(testCase.expectedHost)); + }, + ); + }); } diff --git a/test/headers/basic/if_modified_since_header_test.dart b/test/headers/basic/if_modified_since_header_test.dart index 5511ba60..458e2d85 100644 --- a/test/headers/basic/if_modified_since_header_test.dart +++ b/test/headers/basic/if_modified_since_header_test.dart @@ -68,7 +68,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'if-modified-since': 'invalid-date-format'}, ); @@ -133,35 +133,29 @@ void main() { tearDown(() => server.close()); group('when an empty If-Modified-Since header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-modified-since': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-modified-since': ''}, + ); - expect(Headers.ifModifiedSince[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.ifModifiedSince, throwsInvalidHeader); - }, - ); + expect(Headers.ifModifiedSince[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.ifModifiedSince, throwsInvalidHeader); + }); }); group('when an invalid If-Modified-Since header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-modified-since': 'invalid-date-format'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-modified-since': 'invalid-date-format'}, + ); - expect(Headers.ifModifiedSince[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.ifModifiedSince, throwsInvalidHeader); - }, - ); + expect(Headers.ifModifiedSince[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.ifModifiedSince, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/if_unmodified_since_header_test.dart b/test/headers/basic/if_unmodified_since_header_test.dart index 1946baea..3ae9b458 100644 --- a/test/headers/basic/if_unmodified_since_header_test.dart +++ b/test/headers/basic/if_unmodified_since_header_test.dart @@ -8,123 +8,120 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Unmodified-Since /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given an If-Unmodified-Since header with validation', - () { - late RelicServer server; - - setUp(() async { - server = await createServer(); - }); + group('Given an If-Unmodified-Since header with validation', () { + late RelicServer server; - tearDown(() => server.close()); - - test( - 'when an empty If-Unmodified-Since header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'if-unmodified-since': ''}, - touchHeaders: (final h) => h.ifUnmodifiedSince, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); - - test( - 'when an If-Unmodified-Since header with an invalid date format is passed ' - 'then the server responds with a bad request including a message that ' - 'states the date format is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'if-unmodified-since': 'invalid-date-format'}, - touchHeaders: (final h) => h.ifUnmodifiedSince, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid date format'), - ), - ), - ); - }, - ); - - test( - 'when an If-Unmodified-Since header with an invalid date format is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-unmodified-since': 'invalid-date-format'}, - ); + setUp(() async { + server = await createServer(); + }); - expect(headers, isNotNull); - }, - ); + tearDown(() => server.close()); - test( - 'when a valid If-Unmodified-Since header is passed then it should parse the ' - 'date correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'if-unmodified-since': 'Wed, 21 Oct 2015 07:28:00 GMT'}, - touchHeaders: (final h) => h.ifUnmodifiedSince, - ); - - expect( - headers.ifUnmodifiedSince, - equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), - ); - }, - ); - - test( - 'when an If-Unmodified-Since header with extra whitespace is passed then it should parse the date correctly', - () async { - final headers = await getServerRequestHeaders( + test( + 'when an empty If-Unmodified-Since header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', + () async { + expect( + getServerRequestHeaders( server: server, - headers: {'if-unmodified-since': ' Wed, 21 Oct 2015 07:28:00 GMT '}, + headers: {'if-unmodified-since': ''}, touchHeaders: (final h) => h.ifUnmodifiedSince, - ); - - expect( - headers.ifUnmodifiedSince, - equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), - ); - }, - ); - - test( - 'when no If-Unmodified-Since header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), + ); + }, + ); + + test( + 'when an If-Unmodified-Since header with an invalid date format is passed ' + 'then the server responds with a bad request including a message that ' + 'states the date format is invalid', + () async { + expect( + getServerRequestHeaders( server: server, - headers: {}, + headers: {'if-unmodified-since': 'invalid-date-format'}, touchHeaders: (final h) => h.ifUnmodifiedSince, - ); - - expect(headers.ifUnmodifiedSince, isNull); - }, - ); - }, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid date format'), + ), + ), + ); + }, + ); + + test( + 'when an If-Unmodified-Since header with an invalid date format is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-unmodified-since': 'invalid-date-format'}, + ); + + expect(headers, isNotNull); + }, + ); + + test( + 'when a valid If-Unmodified-Since header is passed then it should parse the ' + 'date correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'if-unmodified-since': 'Wed, 21 Oct 2015 07:28:00 GMT'}, + touchHeaders: (final h) => h.ifUnmodifiedSince, + ); + + expect( + headers.ifUnmodifiedSince, + equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), + ); + }, + ); + + test( + 'when an If-Unmodified-Since header with extra whitespace is passed then it should parse the date correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'if-unmodified-since': ' Wed, 21 Oct 2015 07:28:00 GMT '}, + touchHeaders: (final h) => h.ifUnmodifiedSince, + ); + + expect( + headers.ifUnmodifiedSince, + equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), + ); + }, + ); + + test( + 'when no If-Unmodified-Since header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.ifUnmodifiedSince, + ); + + expect(headers.ifUnmodifiedSince, isNull); + }, + ); + }); group('Given an If-Unmodified-Since header without validation', () { late RelicServer server; @@ -136,37 +133,29 @@ void main() { tearDown(() => server.close()); group('when an empty If-Unmodified-Since header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-unmodified-since': ''}, - ); - - expect( - Headers.ifUnmodifiedSince[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.ifUnmodifiedSince, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-unmodified-since': ''}, + ); + + expect(Headers.ifUnmodifiedSince[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.ifUnmodifiedSince, throwsInvalidHeader); + }); }); group('when an invalid If-Unmodified-Since header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-unmodified-since': 'invalid-date-format'}, - ); - - expect( - Headers.ifUnmodifiedSince[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.ifUnmodifiedSince, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-unmodified-since': 'invalid-date-format'}, + ); + + expect(Headers.ifUnmodifiedSince[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.ifUnmodifiedSince, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/last_modified_header.dart b/test/headers/basic/last_modified_header.dart index 70971ec7..032bb1a2 100644 --- a/test/headers/basic/last_modified_header.dart +++ b/test/headers/basic/last_modified_header.dart @@ -39,59 +39,50 @@ void main() { }, ); - test( - 'when a Last-Modified header with an invalid date format is passed ' - 'then the server responds with a bad request including a message that ' - 'states the date format is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'last-modified': 'invalid-date-format'}, - touchHeaders: (final h) => h.lastModified, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid date format'), - ), - ), - ); - }, - ); - - test( - 'when a Last-Modified header with an invalid date format is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + test('when a Last-Modified header with an invalid date format is passed ' + 'then the server responds with a bad request including a message that ' + 'states the date format is invalid', () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, headers: {'last-modified': 'invalid-date-format'}, - ); + touchHeaders: (final h) => h.lastModified, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid date format'), + ), + ), + ); + }); - expect(headers, isNotNull); - }, - ); + test('when a Last-Modified header with an invalid date format is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'last-modified': 'invalid-date-format'}, + ); - test( - 'when a valid Last-Modified header is passed then it should parse the ' - 'date correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'last-modified': 'Wed, 21 Oct 2015 07:28:00 GMT'}, - touchHeaders: (final h) => h.lastModified, - ); + expect(headers, isNotNull); + }); - expect( - headers.lastModified, - equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), - ); - }, - ); + test('when a valid Last-Modified header is passed then it should parse the ' + 'date correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'last-modified': 'Wed, 21 Oct 2015 07:28:00 GMT'}, + touchHeaders: (final h) => h.lastModified, + ); + + expect( + headers.lastModified, + equals(parseHttpDate('Wed, 21 Oct 2015 07:28:00 GMT')), + ); + }); test( 'when a Last-Modified header with extra whitespace is passed then it should parse the date correctly', @@ -133,35 +124,29 @@ void main() { tearDown(() => server.close()); group('when an empty Last-Modified header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'last-modified': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'last-modified': ''}, + ); - expect(Headers.lastModified[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.lastModified, throwsInvalidHeader); - }, - ); + expect(Headers.lastModified[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.lastModified, throwsInvalidHeader); + }); }); group('when an invalid Last-Modified header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'last-modified': 'invalid-date-format'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'last-modified': 'invalid-date-format'}, + ); - expect(Headers.lastModified[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.lastModified, throwsInvalidHeader); - }, - ); + expect(Headers.lastModified[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.lastModified, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/location_header_test.dart b/test/headers/basic/location_header_test.dart index 2ed33936..aff260c4 100644 --- a/test/headers/basic/location_header_test.dart +++ b/test/headers/basic/location_header_test.dart @@ -7,159 +7,141 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Location header with validation', - () { - late RelicServer server; + group('Given a Location header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when an empty Location header is passed then the server responds ' + test('when an empty Location header is passed then the server responds ' 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'location': ''}, - touchHeaders: (final h) => h.location, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); - - test( - 'when a Location header with an invalid URI is passed then the server ' - 'responds with a bad request including a message that states the URI ' - 'is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'location': 'ht!tp://invalid-url'}, - touchHeaders: (final h) => h.location, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid URI'), - ), - ), - ); - }, + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'location': ''}, + touchHeaders: (final h) => h.location, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); + }); - test( - 'when a Location header with an invalid port is passed then the server ' + test('when a Location header with an invalid URI is passed then the server ' 'responds with a bad request including a message that states the URI ' - 'format is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'location': 'https://example.com:test'}, - touchHeaders: (final h) => h.location, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid URI format'), - ), - ), - ); - }, + 'is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'location': 'ht!tp://invalid-url'}, + touchHeaders: (final h) => h.location, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid URI'), + ), + ), ); + }); - test( - 'when a Location header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + test( + 'when a Location header with an invalid port is passed then the server ' + 'responds with a bad request including a message that states the URI ' + 'format is invalid', + () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, headers: {'location': 'https://example.com:test'}, - ); - - expect(headers, isNotNull); - }, - ); - - test( - 'when a Location header with a valid URI is passed then it should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'location': 'https://example.com/page'}, touchHeaders: (final h) => h.location, - ); - - expect( - headers.location, - equals(Uri.parse('https://example.com/page')), - ); - }, - ); - - test( - 'when a Location header with a valid port is passed then it should parse ' - 'correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'location': 'https://example.com:8080'}, - touchHeaders: (final h) => h.location, - ); - - expect( - headers.location?.port, - equals(8080), - ); - }, - ); - - test( - 'when a Location header with extra whitespace is passed then it should ' - 'parse the URI correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'location': ' https://example.com '}, - touchHeaders: (final h) => h.location, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid URI format'), + ), + ), + ); + }, + ); - expect(headers.location, equals(Uri.parse('https://example.com'))); - }, + test('when a Location header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'location': 'https://example.com:test'}, ); - test( - 'when no Location header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.location, - ); + expect(headers, isNotNull); + }); - expect(headers.location, isNull); - }, - ); - }, - ); + test( + 'when a Location header with a valid URI is passed then it should parse correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'location': 'https://example.com/page'}, + touchHeaders: (final h) => h.location, + ); + + expect(headers.location, equals(Uri.parse('https://example.com/page'))); + }, + ); + + test( + 'when a Location header with a valid port is passed then it should parse ' + 'correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'location': 'https://example.com:8080'}, + touchHeaders: (final h) => h.location, + ); + + expect(headers.location?.port, equals(8080)); + }, + ); + + test( + 'when a Location header with extra whitespace is passed then it should ' + 'parse the URI correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'location': ' https://example.com '}, + touchHeaders: (final h) => h.location, + ); + + expect(headers.location, equals(Uri.parse('https://example.com'))); + }, + ); + + test( + 'when no Location header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.location, + ); + + expect(headers.location, isNull); + }, + ); + }); group('Given a Location header without validation', () { late RelicServer server; @@ -171,19 +153,16 @@ void main() { tearDown(() => server.close()); group('when an invalid Location header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'location': 'ht!tp://invalid-url'}, - ); - - expect(Headers.location[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.location, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'location': 'ht!tp://invalid-url'}, + ); + + expect(Headers.location[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.location, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/max_forwards_header_test.dart b/test/headers/basic/max_forwards_header_test.dart index 440eb432..88ff68a2 100644 --- a/test/headers/basic/max_forwards_header_test.dart +++ b/test/headers/basic/max_forwards_header_test.dart @@ -7,139 +7,127 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Max-Forwards /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Max-Forwards header with validation', - () { - late RelicServer server; + group('Given a Max-Forwards header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when an empty Max-Forwards header is passed then the server responds ' + test('when an empty Max-Forwards header is passed then the server responds ' 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'max-forwards': ''}, - touchHeaders: (final h) => h.maxForwards, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); - - test( - 'when a Max-Forwards header with a negative number is passed then the server ' - 'responds with a bad request including a message that states the value ' - 'must be non-negative', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'max-forwards': '-1'}, - touchHeaders: (final h) => h.maxForwards, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Must be non-negative'), - ), - ), - ); - }, - ); - - test( - 'when a Max-Forwards header with a non-integer value is passed then the server ' - 'responds with a bad request including a message that states the value ' - 'must be an integer', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'max-forwards': '5.5'}, - touchHeaders: (final h) => h.maxForwards, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Must be an integer'), - ), - ), - ); - }, + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'max-forwards': ''}, + touchHeaders: (final h) => h.maxForwards, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); + }); - test( - 'when a Max-Forwards header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + test( + 'when a Max-Forwards header with a negative number is passed then the server ' + 'responds with a bad request including a message that states the value ' + 'must be non-negative', + () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, - headers: {'max-forwards': 'invalid-value'}, - ); - - expect(headers, isNotNull); - }, - ); - - test( - 'when a Max-Forwards header with a valid integer is passed then it ' - 'should parse correctly', - () async { - final headers = await getServerRequestHeaders( + headers: {'max-forwards': '-1'}, + touchHeaders: (final h) => h.maxForwards, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Must be non-negative'), + ), + ), + ); + }, + ); + + test( + 'when a Max-Forwards header with a non-integer value is passed then the server ' + 'responds with a bad request including a message that states the value ' + 'must be an integer', + () async { + expect( + getServerRequestHeaders( server: server, - headers: {'max-forwards': '5'}, + headers: {'max-forwards': '5.5'}, touchHeaders: (final h) => h.maxForwards, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Must be an integer'), + ), + ), + ); + }, + ); - expect(headers.maxForwards, equals(5)); - }, + test('when a Max-Forwards header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'max-forwards': 'invalid-value'}, ); - test( - 'when a Max-Forwards header with zero is passed then it should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'max-forwards': '0'}, - touchHeaders: (final h) => h.maxForwards, - ); + expect(headers, isNotNull); + }); - expect(headers.maxForwards, equals(0)); - }, + test('when a Max-Forwards header with a valid integer is passed then it ' + 'should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'max-forwards': '5'}, + touchHeaders: (final h) => h.maxForwards, ); - test( - 'when no Max-Forwards header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.maxForwards, - ); + expect(headers.maxForwards, equals(5)); + }); - expect(headers.maxForwards, isNull); - }, - ); - }, - ); + test( + 'when a Max-Forwards header with zero is passed then it should parse correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'max-forwards': '0'}, + touchHeaders: (final h) => h.maxForwards, + ); + + expect(headers.maxForwards, equals(0)); + }, + ); + + test( + 'when no Max-Forwards header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.maxForwards, + ); + + expect(headers.maxForwards, isNull); + }, + ); + }); group('Given a Max-Forwards header without validation', () { late RelicServer server; @@ -151,19 +139,16 @@ void main() { tearDown(() => server.close()); group('when an invalid Max-Forwards header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'max-forwards': 'invalid'}, - ); - - expect(Headers.maxForwards[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.maxForwards, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'max-forwards': 'invalid'}, + ); + + expect(Headers.maxForwards[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.maxForwards, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/origin_header_test.dart b/test/headers/basic/origin_header_test.dart index 26cb77a0..b74961fa 100644 --- a/test/headers/basic/origin_header_test.dart +++ b/test/headers/basic/origin_header_test.dart @@ -7,160 +7,136 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given an Origin header with validation', - () { - late RelicServer server; + group('Given an Origin header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when an empty Origin header is passed then the server responds ' + test('when an empty Origin header is passed then the server responds ' 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'origin': ''}, - touchHeaders: (final h) => h.origin, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'origin': ''}, + touchHeaders: (final h) => h.origin, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); + }); - test( - 'when an Origin header with an invalid URI format is passed ' + test('when an Origin header with an invalid URI format is passed ' 'then the server responds with a bad request including a message that ' - 'states the URI format is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'origin': 'h@ttp://example.com'}, - touchHeaders: (final h) => h.origin, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid URI format'), - ), - ), - ); - }, + 'states the URI format is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'origin': 'h@ttp://example.com'}, + touchHeaders: (final h) => h.origin, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid URI format'), + ), + ), ); + }); - test( - 'when an Origin header with an invalid port number is passed ' + test('when an Origin header with an invalid port number is passed ' 'then the server responds with a bad request including a message that ' - 'states the URI format is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'origin': 'http://example.com:test'}, - touchHeaders: (final h) => h.origin, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid URI format'), - ), - ), - ); - }, + 'states the URI format is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'origin': 'http://example.com:test'}, + touchHeaders: (final h) => h.origin, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid URI format'), + ), + ), ); + }); - test( - 'when an Origin header with an invalid origin format is passed ' + test('when an Origin header with an invalid origin format is passed ' 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'origin': 'http://example.com:test'}, - ); - - expect(headers, isNotNull); - }, - ); - - test( - 'when a valid Origin header is passed then it should parse the URI correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'origin': 'https://example.com'}, - touchHeaders: (final h) => h.origin, - ); - - expect( - headers.origin, - equals(Uri.parse('https://example.com')), - ); - }, + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'origin': 'http://example.com:test'}, ); - test( - 'when a valid Origin header is passed with a port number then it should parse the port number correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'origin': 'https://example.com:8080'}, - touchHeaders: (final h) => h.origin, - ); - - expect( - headers.origin?.port, - equals(8080), - ); - }, - ); - - test( - 'when an Origin header with extra whitespace is passed then it should parse the URI correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'origin': ' https://example.com '}, - touchHeaders: (final h) => h.origin, - ); - - expect( - headers.origin, - equals(Uri.parse('https://example.com')), - ); - }, - ); + expect(headers, isNotNull); + }); - test( - 'when no Origin header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.origin, - ); - - expect(headers.origin, isNull); - }, - ); - }, - ); + test( + 'when a valid Origin header is passed then it should parse the URI correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'origin': 'https://example.com'}, + touchHeaders: (final h) => h.origin, + ); + + expect(headers.origin, equals(Uri.parse('https://example.com'))); + }, + ); + + test( + 'when a valid Origin header is passed with a port number then it should parse the port number correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'origin': 'https://example.com:8080'}, + touchHeaders: (final h) => h.origin, + ); + + expect(headers.origin?.port, equals(8080)); + }, + ); + + test( + 'when an Origin header with extra whitespace is passed then it should parse the URI correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'origin': ' https://example.com '}, + touchHeaders: (final h) => h.origin, + ); + + expect(headers.origin, equals(Uri.parse('https://example.com'))); + }, + ); + + test( + 'when no Origin header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.origin, + ); + + expect(headers.origin, isNull); + }, + ); + }); group('Given an Origin header without validation', () { late RelicServer server; @@ -171,34 +147,28 @@ void main() { tearDown(() => server.close()); group('when an empty Origin header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'origin': ''}, - ); - expect(Headers.origin[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.origin, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'origin': ''}, + ); + expect(Headers.origin[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.origin, throwsInvalidHeader); + }); }); group('when an invalid Origin header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'origin': 'h@ttp://example.com'}, - ); - - expect(Headers.origin[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.origin, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'origin': 'h@ttp://example.com'}, + ); + + expect(Headers.origin[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.origin, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/referer_header_test.dart b/test/headers/basic/referer_header_test.dart index d7035cfa..86022681 100644 --- a/test/headers/basic/referer_header_test.dart +++ b/test/headers/basic/referer_header_test.dart @@ -7,158 +7,136 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Referer header with validation', - () { - late RelicServer server; + group('Given a Referer header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when a Referer header with an empty value is passed then the server ' + test('when a Referer header with an empty value is passed then the server ' 'responds with a bad request including a message that states the ' - 'header value cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'referer': ''}, - touchHeaders: (final h) => h.referer, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, + 'header value cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'referer': ''}, + touchHeaders: (final h) => h.referer, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); + }); - test( - 'when a Referer header with an invalid URI is passed then the server ' + test('when a Referer header with an invalid URI is passed then the server ' 'responds with a bad request including a message that states the URI is ' - 'invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'referer': 'ht!tp://invalid-url'}, - touchHeaders: (final h) => h.referer, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid URI'), - ), - ), - ); - }, + 'invalid', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'referer': 'ht!tp://invalid-url'}, + touchHeaders: (final h) => h.referer, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid URI'), + ), + ), ); + }); - test( - 'when a Referer header with an invalid port number is passed ' + test('when a Referer header with an invalid port number is passed ' 'then the server responds with a bad request including a message that ' - 'states the URI format is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'referer': 'http://example.com:test'}, - touchHeaders: (final h) => h.referer, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid URI format'), - ), - ), - ); - }, + 'states the URI format is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'referer': 'http://example.com:test'}, + touchHeaders: (final h) => h.referer, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid URI format'), + ), + ), ); + }); - test( - 'when a Referer header with an invalid value is passed ' + test('when a Referer header with an invalid value is passed ' 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'referer': 'http://example.com:test'}, - ); - - expect(headers, isNotNull); - }, + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'referer': 'http://example.com:test'}, ); - test( - 'when a Referer header with a valid URI is passed then it should parse ' - 'correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'referer': 'https://example.com/page'}, - touchHeaders: (final h) => h.referer, - ); - - expect( - headers.referer, equals(Uri.parse('https://example.com/page'))); - }, - ); + expect(headers, isNotNull); + }); - test( - 'when a Referer header with a port number is passed then it should parse ' - 'the port number correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'referer': 'https://example.com:8080'}, - touchHeaders: (final h) => h.referer, - ); - - expect( - headers.referer?.port, - equals(8080), - ); - }, + test( + 'when a Referer header with a valid URI is passed then it should parse ' + 'correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'referer': 'https://example.com/page'}, + touchHeaders: (final h) => h.referer, + ); + + expect(headers.referer, equals(Uri.parse('https://example.com/page'))); + }, + ); + + test( + 'when a Referer header with a port number is passed then it should parse ' + 'the port number correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'referer': 'https://example.com:8080'}, + touchHeaders: (final h) => h.referer, + ); + + expect(headers.referer?.port, equals(8080)); + }, + ); + + test('when a Referer header with extra whitespace is passed then it should ' + 'parse the URI correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'referer': ' https://example.com '}, + touchHeaders: (final h) => h.referer, ); - test( - 'when a Referer header with extra whitespace is passed then it should ' - 'parse the URI correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'referer': ' https://example.com '}, - touchHeaders: (final h) => h.referer, - ); - - expect(headers.referer, equals(Uri.parse('https://example.com'))); - }, - ); + expect(headers.referer, equals(Uri.parse('https://example.com'))); + }); - test( - 'when no Referer header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.referer, - ); - - expect(headers.referer, isNull); - }, - ); - }, - ); + test( + 'when no Referer header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.referer, + ); + + expect(headers.referer, isNull); + }, + ); + }); group('Given a Referer header without validation', () { late RelicServer server; @@ -170,19 +148,16 @@ void main() { tearDown(() => server.close()); group('when an invalid Referer header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'referer': 'ht!tp://invalid-url'}, - ); - - expect(Headers.referer[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.referer, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'referer': 'ht!tp://invalid-url'}, + ); + + expect(Headers.referer[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.referer, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/server_header_test.dart b/test/headers/basic/server_header_test.dart index e19e5efa..fd8875ce 100644 --- a/test/headers/basic/server_header_test.dart +++ b/test/headers/basic/server_header_test.dart @@ -7,94 +7,85 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Server header with validation', - () { - late RelicServer server; + group('Given a Server header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when an empty Server header is passed then the server responds ' + test('when an empty Server header is passed then the server responds ' 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'server': ''}, - touchHeaders: (final h) => h.server, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'server': ''}, + touchHeaders: (final h) => h.server, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); + }); - test( - 'when a Server header with an empty value is passed ' + test('when a Server header with an empty value is passed ' 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'server': ''}, - ); - - expect(headers, isNotNull); - }, + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'server': ''}, ); - test( - 'when a valid Server header is passed then it should parse the server correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'server': 'MyServer/1.0'}, - ); - - expect(headers.server, equals('MyServer/1.0')); - }, - ); - - test( - 'when a Server header with extra whitespace is passed then it should parse the server correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'server': ' MyServer/1.0 '}, - touchHeaders: (final h) => h.server, - ); - - expect(headers.server, equals('MyServer/1.0')); - }, - ); + expect(headers, isNotNull); + }); - test( - 'when no Server header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.server, - ); - - expect(headers.server, isNull); - }, - ); - }, - ); + test( + 'when a valid Server header is passed then it should parse the server correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'server': 'MyServer/1.0'}, + ); + + expect(headers.server, equals('MyServer/1.0')); + }, + ); + + test( + 'when a Server header with extra whitespace is passed then it should parse the server correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'server': ' MyServer/1.0 '}, + touchHeaders: (final h) => h.server, + ); + + expect(headers.server, equals('MyServer/1.0')); + }, + ); + + test( + 'when no Server header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.server, + ); + + expect(headers.server, isNull); + }, + ); + }); group('Given a Server header without validation', () { late RelicServer server; @@ -106,19 +97,16 @@ void main() { tearDown(() => server.close()); group('when an empty Server header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'server': ''}, - ); - - expect(Headers.server[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.server, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'server': ''}, + ); + + expect(Headers.server[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.server, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/trailer_header_test.dart b/test/headers/basic/trailer_header_test.dart index 9dbdc2b3..6b838493 100644 --- a/test/headers/basic/trailer_header_test.dart +++ b/test/headers/basic/trailer_header_test.dart @@ -26,29 +26,28 @@ void main() { headers: {'trailer': ''}, touchHeaders: (final h) => h.trailer, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); - test( - 'when a Trailer header with an empty value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'trailer': ''}, - ); + test('when a Trailer header with an empty value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'trailer': ''}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid Trailer header is passed then it should parse correctly', @@ -91,10 +90,7 @@ void main() { touchHeaders: (final h) => h.trailer, ); - expect( - headers.trailer, - equals(['custom-header', 'AnotherHeader']), - ); + expect(headers.trailer, equals(['custom-header', 'AnotherHeader'])); }, ); @@ -126,7 +122,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'trailer': 'custom-header'}, ); diff --git a/test/headers/basic/user_agent_header_test.dart b/test/headers/basic/user_agent_header_test.dart index fdb42c27..f3db2564 100644 --- a/test/headers/basic/user_agent_header_test.dart +++ b/test/headers/basic/user_agent_header_test.dart @@ -16,42 +16,36 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty User-Agent header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'user-agent': ''}, - touchHeaders: (final h) => h.userAgent, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); - - test( - 'when a User-Agent header with an empty value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + test('when an empty User-Agent header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, headers: {'user-agent': ''}, - ); + touchHeaders: (final h) => h.userAgent, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), + ); + }); - expect(headers, isNotNull); - }, - ); + test('when a User-Agent header with an empty value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'user-agent': ''}, + ); + + expect(headers, isNotNull); + }); test( 'when a User-Agent string is passed then it should parse correctly', @@ -93,19 +87,16 @@ void main() { tearDown(() => server.close()); group('when an invalid User-Agent header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'user-agent': ''}, - ); - - expect(Headers.userAgent[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.userAgent, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'user-agent': ''}, + ); + + expect(Headers.userAgent[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.userAgent, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/via_header_test.dart b/test/headers/basic/via_header_test.dart index 3a7a76da..2fbb90e6 100644 --- a/test/headers/basic/via_header_test.dart +++ b/test/headers/basic/via_header_test.dart @@ -16,42 +16,36 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty Via header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'via': ''}, - touchHeaders: (final h) => h.via, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); - - test( - 'when a Via header with an empty value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + test('when an empty Via header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, headers: {'via': ''}, - ); + touchHeaders: (final h) => h.via, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), + ); + }); - expect(headers, isNotNull); - }, - ); + test('when a Via header with an empty value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'via': ''}, + ); + + expect(headers, isNotNull); + }); test( 'when a valid Via header is passed then it should parse the values correctly', @@ -93,18 +87,15 @@ void main() { }, ); - test( - 'when no Via header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {}, - touchHeaders: (final h) => h.via, - ); + test('when no Via header is passed then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {}, + touchHeaders: (final h) => h.via, + ); - expect(headers.via, isNull); - }, - ); + expect(headers.via, isNull); + }); }); group('Given a Via header without validation', () { @@ -117,19 +108,16 @@ void main() { tearDown(() => server.close()); group('when an empty Via header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'via': ''}, - ); - - expect(Headers.via[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.via, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'via': ''}, + ); + + expect(Headers.via[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.via, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/basic/x_powered_by_header_test.dart b/test/headers/basic/x_powered_by_header_test.dart index 1e044402..0cc2fd74 100644 --- a/test/headers/basic/x_powered_by_header_test.dart +++ b/test/headers/basic/x_powered_by_header_test.dart @@ -6,56 +6,53 @@ import '../headers_test_utils.dart'; /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given an X-Powered-By header accessor', - () { - late RelicServer server; + group('Given an X-Powered-By header accessor', () { + late RelicServer server; - setUp(() async { - server = await createServer(); + setUp(() async { + server = await createServer(); + }); + + tearDown(() => server.close()); + + test( + 'when setting a valid X-Powered-By value then it should be accessible', + () { + final headers = Headers.build((final h) { + h.xPoweredBy = 'Express'; + }); + + expect(headers.xPoweredBy, equals('Express')); + }, + ); + + test('when setting X-Powered-By to null then it should be null', () { + final headers = Headers.build((final h) { + h.xPoweredBy = null; }); - tearDown(() => server.close()); - - test( - 'when setting a valid X-Powered-By value then it should be accessible', - () { - final headers = Headers.build((final h) { - h.xPoweredBy = 'Express'; - }); - - expect(headers.xPoweredBy, equals('Express')); - }, - ); - - test( - 'when setting X-Powered-By to null then it should be null', - () { - final headers = Headers.build((final h) { - h.xPoweredBy = null; - }); - - expect(headers.xPoweredBy, isNull); - }, - ); - - test( - 'when creating response with X-Powered-By header then it should be preserved', - () { - final response = Response.ok(headers: Headers.build((final h) { + expect(headers.xPoweredBy, isNull); + }); + + test( + 'when creating response with X-Powered-By header then it should be preserved', + () { + final response = Response.ok( + headers: Headers.build((final h) { h.xPoweredBy = 'Custom Framework'; - })); + }), + ); - expect(response.headers.xPoweredBy, equals('Custom Framework')); - }, - ); + expect(response.headers.xPoweredBy, equals('Custom Framework')); + }, + ); - test( - 'when creating response without X-Powered-By header then it should be null', - () { + test( + 'when creating response without X-Powered-By header then it should be null', + () { final response = Response.ok(); expect(response.headers.xPoweredBy, isNull); - }); - }, - ); + }, + ); + }); } diff --git a/test/headers/header_test.dart b/test/headers/header_test.dart index b8cfebf9..b10757e1 100644 --- a/test/headers/header_test.dart +++ b/test/headers/header_test.dart @@ -14,14 +14,14 @@ void main() { test('when custom headers are added then they are included', () { final headers = Headers.fromMap({ - 'X-Custom-Header': ['value'] + 'X-Custom-Header': ['value'], }); expect(headers['x-custom-header'], equals(['value'])); }); test('when custom headers are removed then they are no longer present', () { var headers = Headers.fromMap({ - 'X-Custom-Header': ['value'] + 'X-Custom-Header': ['value'], }); headers = headers.transform((final mh) => mh.remove('X-Custom-Header')); expect(headers['x-custom-header'], isNull); @@ -29,34 +29,38 @@ void main() { test('when accessing headers then they are case insensitive', () { final headers = Headers.fromMap({ - 'Case-Insensitive': ['value'] + 'Case-Insensitive': ['value'], }); expect(headers['case-insensitive'], contains('value')); expect(headers['CASE-INSENSITIVE'], contains('value')); }); - test('when headers are copied then modifications are correctly applied', - () { - final headers = Headers.fromMap({ - 'Initial-Header': ['initial'] - }); - final copiedHeaders = headers.transform((final mh) { - mh.remove('Initial-Header'); - mh['Copied-Header'] = ['copied']; - }); - expect(copiedHeaders['initial-header'], isNull); - expect(copiedHeaders['copied-header'], equals(['copied'])); - }); + test( + 'when headers are copied then modifications are correctly applied', + () { + final headers = Headers.fromMap({ + 'Initial-Header': ['initial'], + }); + final copiedHeaders = headers.transform((final mh) { + mh.remove('Initial-Header'); + mh['Copied-Header'] = ['copied']; + }); + expect(copiedHeaders['initial-header'], isNull); + expect(copiedHeaders['copied-header'], equals(['copied'])); + }, + ); - test('when headers are applied to a Response then they are set correctly', - () { - final headers = Headers.build((final mh) { - mh['Response-Header'] = ['response-value']; - }); - final response = Response.ok(headers: headers); + test( + 'when headers are applied to a Response then they are set correctly', + () { + final headers = Headers.build((final mh) { + mh['Response-Header'] = ['response-value']; + }); + final response = Response.ok(headers: headers); - expect(response.headers['Response-Header'], equals(['response-value'])); - }); + expect(response.headers['Response-Header'], equals(['response-value'])); + }, + ); test('when handling large headers then they are processed correctly', () { final largeValue = List.filled(10000, 'a').join(); @@ -67,15 +71,17 @@ void main() { }); test('when a managed header is removed then it is no longer present', () { - var headers = - Headers.build((final mh) => mh.date = DateTime.utc(2025, 9, 23)); + var headers = Headers.build( + (final mh) => mh.date = DateTime.utc(2025, 9, 23), + ); headers = headers.transform((final mh) => mh.date = null); expect(headers.date, isNull); }); test('when a managed header is updated then it is correctly replaced', () { - var headers = - Headers.build((final mh) => mh.date = DateTime.utc(2025, 9, 23)); + var headers = Headers.build( + (final mh) => mh.date = DateTime.utc(2025, 9, 23), + ); final newDate = DateTime.utc(2025, 9, 23) .add(const Duration(days: 1)) .toUtc() @@ -92,12 +98,21 @@ void main() { final header = v[mh]; singleTest('then raw value not set', mh, isNot(contains(v.key))); singleTest('then not isSet', header.isSet, isFalse); - singleTest('then set fails if type of value wrong', - () => header.set(Object()), throwsA(isA())); - singleTest('then set to null succeeds', () => header.set(null), - returnsNormally); - singleTest('then set succeeds if value is correct type', - () => header.set(header.valueOrNull), returnsNormally); + singleTest( + 'then set fails if type of value wrong', + () => header.set(Object()), + throwsA(isA()), + ); + singleTest( + 'then set to null succeeds', + () => header.set(null), + returnsNormally, + ); + singleTest( + 'then set succeeds if value is correct type', + () => header.set(header.valueOrNull), + returnsNormally, + ); }); }, variants: Headers.all, @@ -113,8 +128,11 @@ void main() { singleTest('then isSet is false', header.isSet, isFalse); singleTest('then isValid is false', header.isValid, isFalse); singleTest('then valueOrNull is null', header.valueOrNull, isNull); - singleTest('then valueOrNullIfInvalid is null', - header.valueOrNullIfInvalid, isNull); + singleTest( + 'then valueOrNullIfInvalid is null', + header.valueOrNullIfInvalid, + isNull, + ); singleTest('then value throws', () => header.value, throwsMissingHeader); }, variants: Headers.all, @@ -124,17 +142,23 @@ void main() { (final v) => 'Given a "${v.key}" header with an empty raw value', (final v) { late final headers = Headers.fromMap({ - v.key: [''] + v.key: [''], }); late final header = v[headers]; singleTest('then raw value set', headers, contains(v.key)); singleTest('then isSet is true', header.isSet, isTrue); singleTest('then isValid is false', header.isValid, isFalse); - singleTest('then valueOrNull throws', () => header.valueOrNull, - throwsInvalidHeader); - singleTest('then valueOrNullIfInvalid is null', - header.valueOrNullIfInvalid, isNull); + singleTest( + 'then valueOrNull throws', + () => header.valueOrNull, + throwsInvalidHeader, + ); + singleTest( + 'then valueOrNullIfInvalid is null', + header.valueOrNullIfInvalid, + isNull, + ); singleTest('then value throws', () => header.value, throwsInvalidHeader); }, variants: Headers.all, @@ -143,14 +167,18 @@ void main() { parameterizedGroup( (final v) => 'Given a "${v.key}" header with an invalid raw value', (final v) { - late final header = v[Headers.fromMap({ - v.key: ['invalid'] - })]; + late final header = + v[Headers.fromMap({ + v.key: ['invalid'], + })]; singleTest('then isSet is true', header.isSet, isTrue); singleTest('then isValid is false', header.isValid, isFalse); - singleTest('then valueOrNull throws', () => header.valueOrNull, - throwsInvalidHeader); + singleTest( + 'then valueOrNull throws', + () => header.valueOrNull, + throwsInvalidHeader, + ); singleTest('then value throws', () => header.value, throwsInvalidHeader); }, variants: Headers.all.difference({ @@ -184,280 +212,295 @@ void main() { ); parameterizedTest( - (final v) => 'Given a "${v.key.key}" header ' + (final v) => + 'Given a "${v.key.key}" header ' 'when using the named extension property on an empty Headers instance ' 'then reading it succeeds and returns null', (final v) { expect(() => v.value(Headers.empty()), returnsNormally); expect(v.value(Headers.empty()), isNull); }, - variants: { - Headers.accept: (final h) => h.accept, - Headers.acceptEncoding: (final h) => h.acceptEncoding, - Headers.acceptLanguage: (final h) => h.acceptLanguage, - Headers.acceptRanges: (final h) => h.acceptRanges, - Headers.accessControlAllowCredentials: (final h) => - h.accessControlAllowCredentials, - Headers.accessControlAllowHeaders: (final h) => - h.accessControlAllowHeaders, - Headers.accessControlAllowMethods: (final h) => - h.accessControlAllowMethods, - Headers.accessControlAllowOrigin: (final h) => h.accessControlAllowOrigin, - Headers.accessControlExposeHeaders: (final h) => - h.accessControlExposeHeaders, - Headers.accessControlMaxAge: (final h) => h.accessControlMaxAge, - Headers.accessControlRequestHeaders: (final h) => - h.accessControlRequestHeaders, - Headers.accessControlRequestMethod: (final h) => - h.accessControlRequestMethod, - Headers.age: (final h) => h.age, - Headers.allow: (final h) => h.allow, - Headers.authorization: (final h) => h.authorization, - Headers.cacheControl: (final h) => h.cacheControl, - Headers.clearSiteData: (final h) => h.clearSiteData, - Headers.connection: (final h) => h.connection, - Headers.contentDisposition: (final h) => h.contentDisposition, - Headers.contentEncoding: (final h) => h.contentEncoding, - Headers.contentLanguage: (final h) => h.contentLanguage, - Headers.contentLength: (final h) => h.contentLength, - Headers.contentLocation: (final h) => h.contentLocation, - Headers.contentRange: (final h) => h.contentRange, - Headers.contentSecurityPolicy: (final h) => h.contentSecurityPolicy, - Headers.cookie: (final h) => h.cookie, - Headers.crossOriginEmbedderPolicy: (final h) => - h.crossOriginEmbedderPolicy, - Headers.crossOriginOpenerPolicy: (final h) => h.crossOriginOpenerPolicy, - Headers.crossOriginResourcePolicy: (final h) => - h.crossOriginResourcePolicy, - Headers.date: (final h) => h.date, - Headers.etag: (final h) => h.etag, - Headers.expect: (final h) => h.expect, - Headers.expires: (final h) => h.expires, - Headers.from: (final h) => h.from, - Headers.host: (final h) => h.host, - Headers.ifMatch: (final h) => h.ifMatch, - Headers.ifModifiedSince: (final h) => h.ifModifiedSince, - Headers.ifNoneMatch: (final h) => h.ifNoneMatch, - Headers.ifRange: (final h) => h.ifRange, - Headers.ifUnmodifiedSince: (final h) => h.ifUnmodifiedSince, - Headers.lastModified: (final h) => h.lastModified, - Headers.location: (final h) => h.location, - Headers.maxForwards: (final h) => h.maxForwards, - Headers.origin: (final h) => h.origin, - Headers.permissionsPolicy: (final h) => h.permissionsPolicy, - Headers.proxyAuthenticate: (final h) => h.proxyAuthenticate, - Headers.proxyAuthorization: (final h) => h.proxyAuthorization, - Headers.range: (final h) => h.range, - Headers.referer: (final h) => h.referer, - Headers.referrerPolicy: (final h) => h.referrerPolicy, - Headers.retryAfter: (final h) => h.retryAfter, - Headers.secFetchDest: (final h) => h.secFetchDest, - Headers.secFetchMode: (final h) => h.secFetchMode, - Headers.secFetchSite: (final h) => h.secFetchSite, - Headers.server: (final h) => h.server, - Headers.setCookie: (final h) => h.setCookie, - Headers.strictTransportSecurity: (final h) => h.strictTransportSecurity, - Headers.te: (final h) => h.te, - Headers.trailer: (final h) => h.trailer, - Headers.transferEncoding: (final h) => h.transferEncoding, - Headers.upgrade: (final h) => h.upgrade, - Headers.userAgent: (final h) => h.userAgent, - Headers.vary: (final h) => h.vary, - Headers.via: (final h) => h.via, - Headers.wwwAuthenticate: (final h) => h.wwwAuthenticate, - Headers.xPoweredBy: (final h) => h.xPoweredBy, - Headers.forwarded: (final h) => h.forwarded, - Headers.xForwardedFor: (final h) => h.xForwardedFor, - }.entries, + variants: + { + Headers.accept: (final h) => h.accept, + Headers.acceptEncoding: (final h) => h.acceptEncoding, + Headers.acceptLanguage: (final h) => h.acceptLanguage, + Headers.acceptRanges: (final h) => h.acceptRanges, + Headers.accessControlAllowCredentials: + (final h) => h.accessControlAllowCredentials, + Headers.accessControlAllowHeaders: + (final h) => h.accessControlAllowHeaders, + Headers.accessControlAllowMethods: + (final h) => h.accessControlAllowMethods, + Headers.accessControlAllowOrigin: + (final h) => h.accessControlAllowOrigin, + Headers.accessControlExposeHeaders: + (final h) => h.accessControlExposeHeaders, + Headers.accessControlMaxAge: (final h) => h.accessControlMaxAge, + Headers.accessControlRequestHeaders: + (final h) => h.accessControlRequestHeaders, + Headers.accessControlRequestMethod: + (final h) => h.accessControlRequestMethod, + Headers.age: (final h) => h.age, + Headers.allow: (final h) => h.allow, + Headers.authorization: (final h) => h.authorization, + Headers.cacheControl: (final h) => h.cacheControl, + Headers.clearSiteData: (final h) => h.clearSiteData, + Headers.connection: (final h) => h.connection, + Headers.contentDisposition: (final h) => h.contentDisposition, + Headers.contentEncoding: (final h) => h.contentEncoding, + Headers.contentLanguage: (final h) => h.contentLanguage, + Headers.contentLength: (final h) => h.contentLength, + Headers.contentLocation: (final h) => h.contentLocation, + Headers.contentRange: (final h) => h.contentRange, + Headers.contentSecurityPolicy: (final h) => h.contentSecurityPolicy, + Headers.cookie: (final h) => h.cookie, + Headers.crossOriginEmbedderPolicy: + (final h) => h.crossOriginEmbedderPolicy, + Headers.crossOriginOpenerPolicy: + (final h) => h.crossOriginOpenerPolicy, + Headers.crossOriginResourcePolicy: + (final h) => h.crossOriginResourcePolicy, + Headers.date: (final h) => h.date, + Headers.etag: (final h) => h.etag, + Headers.expect: (final h) => h.expect, + Headers.expires: (final h) => h.expires, + Headers.from: (final h) => h.from, + Headers.host: (final h) => h.host, + Headers.ifMatch: (final h) => h.ifMatch, + Headers.ifModifiedSince: (final h) => h.ifModifiedSince, + Headers.ifNoneMatch: (final h) => h.ifNoneMatch, + Headers.ifRange: (final h) => h.ifRange, + Headers.ifUnmodifiedSince: (final h) => h.ifUnmodifiedSince, + Headers.lastModified: (final h) => h.lastModified, + Headers.location: (final h) => h.location, + Headers.maxForwards: (final h) => h.maxForwards, + Headers.origin: (final h) => h.origin, + Headers.permissionsPolicy: (final h) => h.permissionsPolicy, + Headers.proxyAuthenticate: (final h) => h.proxyAuthenticate, + Headers.proxyAuthorization: (final h) => h.proxyAuthorization, + Headers.range: (final h) => h.range, + Headers.referer: (final h) => h.referer, + Headers.referrerPolicy: (final h) => h.referrerPolicy, + Headers.retryAfter: (final h) => h.retryAfter, + Headers.secFetchDest: (final h) => h.secFetchDest, + Headers.secFetchMode: (final h) => h.secFetchMode, + Headers.secFetchSite: (final h) => h.secFetchSite, + Headers.server: (final h) => h.server, + Headers.setCookie: (final h) => h.setCookie, + Headers.strictTransportSecurity: + (final h) => h.strictTransportSecurity, + Headers.te: (final h) => h.te, + Headers.trailer: (final h) => h.trailer, + Headers.transferEncoding: (final h) => h.transferEncoding, + Headers.upgrade: (final h) => h.upgrade, + Headers.userAgent: (final h) => h.userAgent, + Headers.vary: (final h) => h.vary, + Headers.via: (final h) => h.via, + Headers.wwwAuthenticate: (final h) => h.wwwAuthenticate, + Headers.xPoweredBy: (final h) => h.xPoweredBy, + Headers.forwarded: (final h) => h.forwarded, + Headers.xForwardedFor: (final h) => h.xForwardedFor, + }.entries, ); parameterizedTest( - (final v) => 'Given a "${v.key.key}" header ' + (final v) => + 'Given a "${v.key.key}" header ' 'when using the named extension property on an empty MutableHeaders instance ' 'then reading it succeeds and returns null', (final v) { expect(() => Headers.build((final mh) => v.value(mh)), returnsNormally); Headers.build((final mh) => expect(v.value(mh), isNull)); }, - variants: { - Headers.accept: (final h) => h.accept, - Headers.acceptEncoding: (final h) => h.acceptEncoding, - Headers.acceptLanguage: (final h) => h.acceptLanguage, - Headers.acceptRanges: (final h) => h.acceptRanges, - Headers.accessControlAllowCredentials: (final h) => - h.accessControlAllowCredentials, - Headers.accessControlAllowHeaders: (final h) => - h.accessControlAllowHeaders, - Headers.accessControlAllowMethods: (final h) => - h.accessControlAllowMethods, - Headers.accessControlAllowOrigin: (final h) => h.accessControlAllowOrigin, - Headers.accessControlExposeHeaders: (final h) => - h.accessControlExposeHeaders, - Headers.accessControlMaxAge: (final h) => h.accessControlMaxAge, - Headers.accessControlRequestHeaders: (final h) => - h.accessControlRequestHeaders, - Headers.accessControlRequestMethod: (final h) => - h.accessControlRequestMethod, - Headers.age: (final h) => h.age, - Headers.allow: (final h) => h.allow, - Headers.authorization: (final h) => h.authorization, - Headers.cacheControl: (final h) => h.cacheControl, - Headers.clearSiteData: (final h) => h.clearSiteData, - Headers.connection: (final h) => h.connection, - Headers.contentDisposition: (final h) => h.contentDisposition, - Headers.contentEncoding: (final h) => h.contentEncoding, - Headers.contentLanguage: (final h) => h.contentLanguage, - Headers.contentLength: (final h) => h.contentLength, - Headers.contentLocation: (final h) => h.contentLocation, - Headers.contentRange: (final h) => h.contentRange, - Headers.contentSecurityPolicy: (final h) => h.contentSecurityPolicy, - Headers.cookie: (final h) => h.cookie, - Headers.crossOriginEmbedderPolicy: (final h) => - h.crossOriginEmbedderPolicy, - Headers.crossOriginOpenerPolicy: (final h) => h.crossOriginOpenerPolicy, - Headers.crossOriginResourcePolicy: (final h) => - h.crossOriginResourcePolicy, - Headers.date: (final h) => h.date, - Headers.etag: (final h) => h.etag, - Headers.expect: (final h) => h.expect, - Headers.expires: (final h) => h.expires, - Headers.from: (final h) => h.from, - Headers.host: (final h) => h.host, - Headers.ifMatch: (final h) => h.ifMatch, - Headers.ifModifiedSince: (final h) => h.ifModifiedSince, - Headers.ifNoneMatch: (final h) => h.ifNoneMatch, - Headers.ifRange: (final h) => h.ifRange, - Headers.ifUnmodifiedSince: (final h) => h.ifUnmodifiedSince, - Headers.lastModified: (final h) => h.lastModified, - Headers.location: (final h) => h.location, - Headers.maxForwards: (final h) => h.maxForwards, - Headers.origin: (final h) => h.origin, - Headers.permissionsPolicy: (final h) => h.permissionsPolicy, - Headers.proxyAuthenticate: (final h) => h.proxyAuthenticate, - Headers.proxyAuthorization: (final h) => h.proxyAuthorization, - Headers.range: (final h) => h.range, - Headers.referer: (final h) => h.referer, - Headers.referrerPolicy: (final h) => h.referrerPolicy, - Headers.retryAfter: (final h) => h.retryAfter, - Headers.secFetchDest: (final h) => h.secFetchDest, - Headers.secFetchMode: (final h) => h.secFetchMode, - Headers.secFetchSite: (final h) => h.secFetchSite, - Headers.server: (final h) => h.server, - Headers.setCookie: (final h) => h.setCookie, - Headers.strictTransportSecurity: (final h) => h.strictTransportSecurity, - Headers.te: (final h) => h.te, - Headers.trailer: (final h) => h.trailer, - Headers.transferEncoding: (final h) => h.transferEncoding, - Headers.upgrade: (final h) => h.upgrade, - Headers.userAgent: (final h) => h.userAgent, - Headers.vary: (final h) => h.vary, - Headers.via: (final h) => h.via, - Headers.wwwAuthenticate: (final h) => h.wwwAuthenticate, - Headers.xPoweredBy: (final h) => h.xPoweredBy, - Headers.forwarded: (final h) => h.forwarded, - Headers.xForwardedFor: (final h) => h.xForwardedFor, - }.entries, + variants: + { + Headers.accept: (final h) => h.accept, + Headers.acceptEncoding: (final h) => h.acceptEncoding, + Headers.acceptLanguage: (final h) => h.acceptLanguage, + Headers.acceptRanges: (final h) => h.acceptRanges, + Headers.accessControlAllowCredentials: + (final h) => h.accessControlAllowCredentials, + Headers.accessControlAllowHeaders: + (final h) => h.accessControlAllowHeaders, + Headers.accessControlAllowMethods: + (final h) => h.accessControlAllowMethods, + Headers.accessControlAllowOrigin: + (final h) => h.accessControlAllowOrigin, + Headers.accessControlExposeHeaders: + (final h) => h.accessControlExposeHeaders, + Headers.accessControlMaxAge: (final h) => h.accessControlMaxAge, + Headers.accessControlRequestHeaders: + (final h) => h.accessControlRequestHeaders, + Headers.accessControlRequestMethod: + (final h) => h.accessControlRequestMethod, + Headers.age: (final h) => h.age, + Headers.allow: (final h) => h.allow, + Headers.authorization: (final h) => h.authorization, + Headers.cacheControl: (final h) => h.cacheControl, + Headers.clearSiteData: (final h) => h.clearSiteData, + Headers.connection: (final h) => h.connection, + Headers.contentDisposition: (final h) => h.contentDisposition, + Headers.contentEncoding: (final h) => h.contentEncoding, + Headers.contentLanguage: (final h) => h.contentLanguage, + Headers.contentLength: (final h) => h.contentLength, + Headers.contentLocation: (final h) => h.contentLocation, + Headers.contentRange: (final h) => h.contentRange, + Headers.contentSecurityPolicy: (final h) => h.contentSecurityPolicy, + Headers.cookie: (final h) => h.cookie, + Headers.crossOriginEmbedderPolicy: + (final h) => h.crossOriginEmbedderPolicy, + Headers.crossOriginOpenerPolicy: + (final h) => h.crossOriginOpenerPolicy, + Headers.crossOriginResourcePolicy: + (final h) => h.crossOriginResourcePolicy, + Headers.date: (final h) => h.date, + Headers.etag: (final h) => h.etag, + Headers.expect: (final h) => h.expect, + Headers.expires: (final h) => h.expires, + Headers.from: (final h) => h.from, + Headers.host: (final h) => h.host, + Headers.ifMatch: (final h) => h.ifMatch, + Headers.ifModifiedSince: (final h) => h.ifModifiedSince, + Headers.ifNoneMatch: (final h) => h.ifNoneMatch, + Headers.ifRange: (final h) => h.ifRange, + Headers.ifUnmodifiedSince: (final h) => h.ifUnmodifiedSince, + Headers.lastModified: (final h) => h.lastModified, + Headers.location: (final h) => h.location, + Headers.maxForwards: (final h) => h.maxForwards, + Headers.origin: (final h) => h.origin, + Headers.permissionsPolicy: (final h) => h.permissionsPolicy, + Headers.proxyAuthenticate: (final h) => h.proxyAuthenticate, + Headers.proxyAuthorization: (final h) => h.proxyAuthorization, + Headers.range: (final h) => h.range, + Headers.referer: (final h) => h.referer, + Headers.referrerPolicy: (final h) => h.referrerPolicy, + Headers.retryAfter: (final h) => h.retryAfter, + Headers.secFetchDest: (final h) => h.secFetchDest, + Headers.secFetchMode: (final h) => h.secFetchMode, + Headers.secFetchSite: (final h) => h.secFetchSite, + Headers.server: (final h) => h.server, + Headers.setCookie: (final h) => h.setCookie, + Headers.strictTransportSecurity: + (final h) => h.strictTransportSecurity, + Headers.te: (final h) => h.te, + Headers.trailer: (final h) => h.trailer, + Headers.transferEncoding: (final h) => h.transferEncoding, + Headers.upgrade: (final h) => h.upgrade, + Headers.userAgent: (final h) => h.userAgent, + Headers.vary: (final h) => h.vary, + Headers.via: (final h) => h.via, + Headers.wwwAuthenticate: (final h) => h.wwwAuthenticate, + Headers.xPoweredBy: (final h) => h.xPoweredBy, + Headers.forwarded: (final h) => h.forwarded, + Headers.xForwardedFor: (final h) => h.xForwardedFor, + }.entries, ); parameterizedTest( - (final v) => 'Given a "${v.key.key}" header ' + (final v) => + 'Given a "${v.key.key}" header ' 'when using the named extension property on an empty MutableHeaders instance ' 'then setting to null succeeds', (final v) { expect(() => Headers.build((final mh) => v.value(mh)), returnsNormally); }, - variants: { - Headers.accept: (final h) => h.accept = null, - Headers.acceptEncoding: (final h) => h.acceptEncoding = null, - Headers.acceptLanguage: (final h) => h.acceptLanguage = null, - Headers.acceptRanges: (final h) => h.acceptRanges = null, - Headers.accessControlAllowCredentials: (final h) => - h.accessControlAllowCredentials = null, - Headers.accessControlAllowHeaders: (final h) => - h.accessControlAllowHeaders = null, - Headers.accessControlAllowMethods: (final h) => - h.accessControlAllowMethods = null, - Headers.accessControlAllowOrigin: (final h) => - h.accessControlAllowOrigin = null, - Headers.accessControlExposeHeaders: (final h) => - h.accessControlExposeHeaders = null, - Headers.accessControlMaxAge: (final h) => h.accessControlMaxAge = null, - Headers.accessControlRequestHeaders: (final h) => - h.accessControlRequestHeaders = null, - Headers.accessControlRequestMethod: (final h) => - h.accessControlRequestMethod = null, - Headers.age: (final h) => h.age = null, - Headers.allow: (final h) => h.allow = null, - Headers.authorization: (final h) => h.authorization = null, - Headers.cacheControl: (final h) => h.cacheControl = null, - Headers.clearSiteData: (final h) => h.clearSiteData = null, - Headers.connection: (final h) => h.connection = null, - Headers.contentDisposition: (final h) => h.contentDisposition = null, - Headers.contentEncoding: (final h) => h.contentEncoding = null, - Headers.contentLanguage: (final h) => h.contentLanguage = null, - Headers.contentLength: (final h) => h.contentLength = null, - Headers.contentLocation: (final h) => h.contentLocation = null, - Headers.contentRange: (final h) => h.contentRange = null, - Headers.contentSecurityPolicy: (final h) => - h.contentSecurityPolicy = null, - Headers.cookie: (final h) => h.cookie = null, - Headers.crossOriginEmbedderPolicy: (final h) => - h.crossOriginEmbedderPolicy = null, - Headers.crossOriginOpenerPolicy: (final h) => - h.crossOriginOpenerPolicy = null, - Headers.crossOriginResourcePolicy: (final h) => - h.crossOriginResourcePolicy = null, - Headers.date: (final h) => h.date = null, - Headers.etag: (final h) => h.etag = null, - Headers.expect: (final h) => h.expect = null, - Headers.expires: (final h) => h.expires = null, - Headers.from: (final h) => h.from = null, - Headers.host: (final h) => h.host = null, - Headers.ifMatch: (final h) => h.ifMatch = null, - Headers.ifModifiedSince: (final h) => h.ifModifiedSince = null, - Headers.ifNoneMatch: (final h) => h.ifNoneMatch = null, - Headers.ifRange: (final h) => h.ifRange = null, - Headers.ifUnmodifiedSince: (final h) => h.ifUnmodifiedSince = null, - Headers.lastModified: (final h) => h.lastModified = null, - Headers.location: (final h) => h.location = null, - Headers.maxForwards: (final h) => h.maxForwards = null, - Headers.origin: (final h) => h.origin = null, - Headers.permissionsPolicy: (final h) => h.permissionsPolicy = null, - Headers.proxyAuthenticate: (final h) => h.proxyAuthenticate = null, - Headers.proxyAuthorization: (final h) => h.proxyAuthorization = null, - Headers.range: (final h) => h.range = null, - Headers.referer: (final h) => h.referer = null, - Headers.referrerPolicy: (final h) => h.referrerPolicy = null, - Headers.retryAfter: (final h) => h.retryAfter = null, - Headers.secFetchDest: (final h) => h.secFetchDest = null, - Headers.secFetchMode: (final h) => h.secFetchMode = null, - Headers.secFetchSite: (final h) => h.secFetchSite = null, - Headers.server: (final h) => h.server = null, - Headers.setCookie: (final h) => h.setCookie = null, - Headers.strictTransportSecurity: (final h) => - h.strictTransportSecurity = null, - Headers.te: (final h) => h.te = null, - Headers.trailer: (final h) => h.trailer = null, - Headers.transferEncoding: (final h) => h.transferEncoding = null, - Headers.upgrade: (final h) => h.upgrade = null, - Headers.userAgent: (final h) => h.userAgent = null, - Headers.vary: (final h) => h.vary = null, - Headers.via: (final h) => h.via = null, - Headers.wwwAuthenticate: (final h) => h.wwwAuthenticate = null, - Headers.xPoweredBy: (final h) => h.xPoweredBy = null, - Headers.forwarded: (final h) => h.forwarded = null, - Headers.xForwardedFor: (final h) => h.xForwardedFor = null, - }.entries, + variants: + { + Headers.accept: (final h) => h.accept = null, + Headers.acceptEncoding: (final h) => h.acceptEncoding = null, + Headers.acceptLanguage: (final h) => h.acceptLanguage = null, + Headers.acceptRanges: (final h) => h.acceptRanges = null, + Headers.accessControlAllowCredentials: + (final h) => h.accessControlAllowCredentials = null, + Headers.accessControlAllowHeaders: + (final h) => h.accessControlAllowHeaders = null, + Headers.accessControlAllowMethods: + (final h) => h.accessControlAllowMethods = null, + Headers.accessControlAllowOrigin: + (final h) => h.accessControlAllowOrigin = null, + Headers.accessControlExposeHeaders: + (final h) => h.accessControlExposeHeaders = null, + Headers.accessControlMaxAge: + (final h) => h.accessControlMaxAge = null, + Headers.accessControlRequestHeaders: + (final h) => h.accessControlRequestHeaders = null, + Headers.accessControlRequestMethod: + (final h) => h.accessControlRequestMethod = null, + Headers.age: (final h) => h.age = null, + Headers.allow: (final h) => h.allow = null, + Headers.authorization: (final h) => h.authorization = null, + Headers.cacheControl: (final h) => h.cacheControl = null, + Headers.clearSiteData: (final h) => h.clearSiteData = null, + Headers.connection: (final h) => h.connection = null, + Headers.contentDisposition: (final h) => h.contentDisposition = null, + Headers.contentEncoding: (final h) => h.contentEncoding = null, + Headers.contentLanguage: (final h) => h.contentLanguage = null, + Headers.contentLength: (final h) => h.contentLength = null, + Headers.contentLocation: (final h) => h.contentLocation = null, + Headers.contentRange: (final h) => h.contentRange = null, + Headers.contentSecurityPolicy: + (final h) => h.contentSecurityPolicy = null, + Headers.cookie: (final h) => h.cookie = null, + Headers.crossOriginEmbedderPolicy: + (final h) => h.crossOriginEmbedderPolicy = null, + Headers.crossOriginOpenerPolicy: + (final h) => h.crossOriginOpenerPolicy = null, + Headers.crossOriginResourcePolicy: + (final h) => h.crossOriginResourcePolicy = null, + Headers.date: (final h) => h.date = null, + Headers.etag: (final h) => h.etag = null, + Headers.expect: (final h) => h.expect = null, + Headers.expires: (final h) => h.expires = null, + Headers.from: (final h) => h.from = null, + Headers.host: (final h) => h.host = null, + Headers.ifMatch: (final h) => h.ifMatch = null, + Headers.ifModifiedSince: (final h) => h.ifModifiedSince = null, + Headers.ifNoneMatch: (final h) => h.ifNoneMatch = null, + Headers.ifRange: (final h) => h.ifRange = null, + Headers.ifUnmodifiedSince: (final h) => h.ifUnmodifiedSince = null, + Headers.lastModified: (final h) => h.lastModified = null, + Headers.location: (final h) => h.location = null, + Headers.maxForwards: (final h) => h.maxForwards = null, + Headers.origin: (final h) => h.origin = null, + Headers.permissionsPolicy: (final h) => h.permissionsPolicy = null, + Headers.proxyAuthenticate: (final h) => h.proxyAuthenticate = null, + Headers.proxyAuthorization: (final h) => h.proxyAuthorization = null, + Headers.range: (final h) => h.range = null, + Headers.referer: (final h) => h.referer = null, + Headers.referrerPolicy: (final h) => h.referrerPolicy = null, + Headers.retryAfter: (final h) => h.retryAfter = null, + Headers.secFetchDest: (final h) => h.secFetchDest = null, + Headers.secFetchMode: (final h) => h.secFetchMode = null, + Headers.secFetchSite: (final h) => h.secFetchSite = null, + Headers.server: (final h) => h.server = null, + Headers.setCookie: (final h) => h.setCookie = null, + Headers.strictTransportSecurity: + (final h) => h.strictTransportSecurity = null, + Headers.te: (final h) => h.te = null, + Headers.trailer: (final h) => h.trailer = null, + Headers.transferEncoding: (final h) => h.transferEncoding = null, + Headers.upgrade: (final h) => h.upgrade = null, + Headers.userAgent: (final h) => h.userAgent = null, + Headers.vary: (final h) => h.vary = null, + Headers.via: (final h) => h.via = null, + Headers.wwwAuthenticate: (final h) => h.wwwAuthenticate = null, + Headers.xPoweredBy: (final h) => h.xPoweredBy = null, + Headers.forwarded: (final h) => h.forwarded = null, + Headers.xForwardedFor: (final h) => h.xForwardedFor = null, + }.entries, ); parameterizedGroup( (final v) => 'Given a "${v.accessor.key}" header ', (final v) { test( - 'when using the named extension property on an empty MutableHeaders instance ' - 'then setting to value succeeds', () { - expect(() => Headers.build(v.mutator), returnsNormally); - }); + 'when using the named extension property on an empty MutableHeaders instance ' + 'then setting to value succeeds', + () { + expect(() => Headers.build(v.mutator), returnsNormally); + }, + ); test('when round-tripping', () { final headers1 = Headers.build(v.mutator); @@ -486,8 +529,11 @@ void main() { if (header1 is! List && header1 is! Set) { // We don't control hashCode for pure List and Set. expect(header1, equals(header2)); - expect(header1.hashCode, equals(header2.hashCode), - reason: 'hashCode for: $header1'); + expect( + header1.hashCode, + equals(header2.hashCode), + reason: 'hashCode for: $header1', + ); } final raw = v.accessor.codec.encode(header1!); @@ -512,117 +558,139 @@ void main() { ( Headers.accept, (final h) => - h.accept = AcceptHeader.parse(['application/vnd.example.api+json']) + h.accept = AcceptHeader.parse(['application/vnd.example.api+json']), ), ( Headers.acceptEncoding, - (final h) => h.acceptEncoding = const AcceptEncodingHeader.wildcard() + (final h) => h.acceptEncoding = const AcceptEncodingHeader.wildcard(), ), ( Headers.acceptEncoding, - (final h) => h.acceptEncoding = AcceptEncodingHeader.encodings( - encodings: [EncodingQuality('jpeg', 0.5)]) + (final h) => + h.acceptEncoding = AcceptEncodingHeader.encodings( + encodings: [EncodingQuality('jpeg', 0.5)], + ), ), ( Headers.acceptLanguage, - (final h) => h.acceptLanguage = const AcceptLanguageHeader.wildcard() + (final h) => h.acceptLanguage = const AcceptLanguageHeader.wildcard(), ), ( Headers.acceptLanguage, - (final h) => h.acceptLanguage = AcceptLanguageHeader.languages( - languages: [const LanguageQuality('en', 1.0)]) + (final h) => + h.acceptLanguage = AcceptLanguageHeader.languages( + languages: [const LanguageQuality('en', 1.0)], + ), ), ( Headers.acceptRanges, - (final h) => h.acceptRanges = AcceptRangesHeader.none() + (final h) => h.acceptRanges = AcceptRangesHeader.none(), ), ( Headers.accessControlAllowCredentials, - (final h) => h.accessControlAllowCredentials = true + (final h) => h.accessControlAllowCredentials = true, ), ( Headers.accessControlAllowHeaders, - (final h) => h.accessControlAllowHeaders = - const AccessControlAllowHeadersHeader.wildcard() + (final h) => + h.accessControlAllowHeaders = + const AccessControlAllowHeadersHeader.wildcard(), ), ( Headers.accessControlAllowHeaders, - (final h) => h.accessControlAllowHeaders = - AccessControlAllowHeadersHeader.headers(headers: ['foo']) + (final h) => + h.accessControlAllowHeaders = + AccessControlAllowHeadersHeader.headers(headers: ['foo']), ), ( Headers.accessControlAllowMethods, - (final h) => h.accessControlAllowMethods = - const AccessControlAllowMethodsHeader.wildcard() + (final h) => + h.accessControlAllowMethods = + const AccessControlAllowMethodsHeader.wildcard(), ), ( Headers.accessControlAllowMethods, - (final h) => h.accessControlAllowMethods = - AccessControlAllowMethodsHeader.methods(methods: Method.values) + (final h) => + h.accessControlAllowMethods = + AccessControlAllowMethodsHeader.methods(methods: Method.values), ), ( Headers.accessControlAllowOrigin, - (final h) => h.accessControlAllowOrigin = - AccessControlAllowOriginHeader.origin( - origin: Uri.parse('https://example.com')) + (final h) => + h.accessControlAllowOrigin = AccessControlAllowOriginHeader.origin( + origin: Uri.parse('https://example.com'), + ), ), ( Headers.accessControlExposeHeaders, - (final h) => h.accessControlExposeHeaders = - AccessControlExposeHeadersHeader.headers(headers: ['foo']) + (final h) => + h.accessControlExposeHeaders = + AccessControlExposeHeadersHeader.headers(headers: ['foo']), ), (Headers.accessControlMaxAge, (final h) => h.accessControlMaxAge = 42), ( Headers.accessControlRequestHeaders, - (final h) => h.accessControlRequestHeaders = ['foo'] + (final h) => h.accessControlRequestHeaders = ['foo'], ), ( Headers.accessControlRequestMethod, - (final h) => h.accessControlRequestMethod = Method.get + (final h) => h.accessControlRequestMethod = Method.get, ), (Headers.age, (final h) => h.age = 42), (Headers.allow, (final h) => h.allow = {Method.get}), ( Headers.authorization, (final h) => - h.authorization = BearerAuthorizationHeader(token: 'foobar') + h.authorization = BearerAuthorizationHeader(token: 'foobar'), ), ( Headers.authorization, - (final h) => h.authorization = - BasicAuthorizationHeader(username: 'foo', password: 'bar') + (final h) => + h.authorization = BasicAuthorizationHeader( + username: 'foo', + password: 'bar', + ), ), ( Headers.authorization, - (final h) => h.authorization = DigestAuthorizationHeader( - username: 'foo', - realm: 'bar', - nonce: 'random', - uri: 'https://example.com', - response: 'modnar') + (final h) => + h.authorization = DigestAuthorizationHeader( + username: 'foo', + realm: 'bar', + nonce: 'random', + uri: 'https://example.com', + response: 'modnar', + ), ), ( Headers.cacheControl, - (final h) => h.cacheControl = CacheControlHeader.parse(['max-age=3600']) + (final h) => + h.cacheControl = CacheControlHeader.parse(['max-age=3600']), ), ( Headers.clearSiteData, - (final h) => h.clearSiteData = const ClearSiteDataHeader.wildcard() + (final h) => h.clearSiteData = const ClearSiteDataHeader.wildcard(), ), ( Headers.clearSiteData, - (final h) => h.clearSiteData = - ClearSiteDataHeader.dataTypes(dataTypes: [ClearSiteDataType.cache]) + (final h) => + h.clearSiteData = ClearSiteDataHeader.dataTypes( + dataTypes: [ClearSiteDataType.cache], + ), ), ( Headers.connection, - (final h) => h.connection = - const ConnectionHeader(directives: [ConnectionHeaderType.keepAlive]) + (final h) => + h.connection = const ConnectionHeader( + directives: [ConnectionHeaderType.keepAlive], + ), ), ( Headers.contentDisposition, - (final h) => h.contentDisposition = - ContentDispositionHeader.parse('attachment; filename="report.pdf"') + (final h) => + h.contentDisposition = ContentDispositionHeader.parse( + 'attachment; filename="report.pdf"', + ), ), ( Headers.contentDisposition, @@ -631,56 +699,68 @@ void main() { type: 'attachment', parameters: [ ContentDispositionParameter( - name: 'filename', value: 'report.pdf', isExtended: true) + name: 'filename', + value: 'report.pdf', + isExtended: true, + ), ], ); - } + }, ), ( Headers.contentEncoding, - (final h) => h.contentEncoding = - ContentEncodingHeader(encodings: [ContentEncoding.gzip]) + (final h) => + h.contentEncoding = ContentEncodingHeader( + encodings: [ContentEncoding.gzip], + ), ), ( Headers.contentLanguage, (final h) => - h.contentLanguage = ContentLanguageHeader(languages: ['en']) + h.contentLanguage = ContentLanguageHeader(languages: ['en']), ), (Headers.contentLength, (final h) => h.contentLength = 1202), ( Headers.contentLocation, - (final h) => h.contentLocation = Uri.parse('https://example.com') + (final h) => h.contentLocation = Uri.parse('https://example.com'), ), ( Headers.contentRange, - (final h) => h.contentRange = ContentRangeHeader() + (final h) => h.contentRange = ContentRangeHeader(), ), ( Headers.contentSecurityPolicy, - (final h) => h.contentSecurityPolicy = ContentSecurityPolicyHeader( - directives: [ - ContentSecurityPolicyDirective(name: 'foo', values: []) - ]) + (final h) => + h.contentSecurityPolicy = ContentSecurityPolicyHeader( + directives: [ + ContentSecurityPolicyDirective(name: 'foo', values: []), + ], + ), ), ( Headers.cookie, - (final h) => h.cookie = - CookieHeader(cookies: [Cookie(name: 'foo', value: 'bar')]) + (final h) => + h.cookie = CookieHeader( + cookies: [Cookie(name: 'foo', value: 'bar')], + ), ), ( Headers.crossOriginEmbedderPolicy, - (final h) => h.crossOriginEmbedderPolicy = - CrossOriginEmbedderPolicyHeader.unsafeNone + (final h) => + h.crossOriginEmbedderPolicy = + CrossOriginEmbedderPolicyHeader.unsafeNone, ), ( Headers.crossOriginOpenerPolicy, (final h) => - h.crossOriginOpenerPolicy = CrossOriginOpenerPolicyHeader.unsafeNone + h.crossOriginOpenerPolicy = + CrossOriginOpenerPolicyHeader.unsafeNone, ), ( Headers.crossOriginResourcePolicy, - (final h) => h.crossOriginResourcePolicy = - CrossOriginResourcePolicyHeader.sameSite + (final h) => + h.crossOriginResourcePolicy = + CrossOriginResourcePolicyHeader.sameSite, ), (Headers.date, (final h) => h.date = DateTime.utc(2025, 9, 23)), (Headers.etag, (final h) => h.etag = const ETagHeader(value: '')), @@ -688,135 +768,152 @@ void main() { (Headers.expires, (final h) => h.expires = DateTime.utc(2025, 9, 23)), ( Headers.from, - (final h) => h.from = FromHeader(emails: ['info@serverpod.com']) + (final h) => h.from = FromHeader(emails: ['info@serverpod.com']), ), (Headers.host, (final h) => h.host = HostHeader('www.example.com', 80)), ( Headers.ifMatch, - (final h) => h.ifMatch = const IfMatchHeader.wildcard() + (final h) => h.ifMatch = const IfMatchHeader.wildcard(), ), ( Headers.ifMatch, (final h) => - h.ifMatch = IfMatchHeader.etags([const ETagHeader(value: 'foobar')]) + h.ifMatch = IfMatchHeader.etags([ + const ETagHeader(value: 'foobar'), + ]), ), ( Headers.ifModifiedSince, - (final h) => h.ifModifiedSince = DateTime.utc(2025, 9, 23) + (final h) => h.ifModifiedSince = DateTime.utc(2025, 9, 23), ), ( Headers.ifNoneMatch, - (final h) => h.ifNoneMatch = const IfNoneMatchHeader.wildcard() + (final h) => h.ifNoneMatch = const IfNoneMatchHeader.wildcard(), ), ( Headers.ifRange, (final h) => - h.ifRange = IfRangeHeader(lastModified: DateTime.utc(2025, 9, 23)) + h.ifRange = IfRangeHeader(lastModified: DateTime.utc(2025, 9, 23)), ), ( Headers.ifUnmodifiedSince, - (final h) => h.ifUnmodifiedSince = DateTime.utc(2025, 9, 23) + (final h) => h.ifUnmodifiedSince = DateTime.utc(2025, 9, 23), ), ( Headers.lastModified, - (final h) => h.lastModified = DateTime.utc(2025, 9, 23) + (final h) => h.lastModified = DateTime.utc(2025, 9, 23), ), ( Headers.location, - (final h) => h.location = Uri.parse('https://example.com') + (final h) => h.location = Uri.parse('https://example.com'), ), (Headers.maxForwards, (final h) => h.maxForwards = 42), ( Headers.origin, - (final h) => h.origin = Uri.parse('https://example.com') + (final h) => h.origin = Uri.parse('https://example.com'), ), ( Headers.permissionsPolicy, - (final h) => h.permissionsPolicy = PermissionsPolicyHeader(directives: [ - const PermissionsPolicyDirective(name: 'foo', values: []) - ]) + (final h) => + h.permissionsPolicy = PermissionsPolicyHeader( + directives: [ + const PermissionsPolicyDirective(name: 'foo', values: []), + ], + ), ), ( Headers.proxyAuthenticate, - (final h) => h.proxyAuthenticate = AuthenticationHeader( - scheme: 'Bearer', - parameters: [const AuthenticationParameter('foo', 'bar')]) + (final h) => + h.proxyAuthenticate = AuthenticationHeader( + scheme: 'Bearer', + parameters: [const AuthenticationParameter('foo', 'bar')], + ), ), ( Headers.proxyAuthorization, (final h) => - h.proxyAuthorization = BearerAuthorizationHeader(token: 'foobar') + h.proxyAuthorization = BearerAuthorizationHeader(token: 'foobar'), ), ( Headers.range, - (final h) => h.range = RangeHeader(ranges: [Range(start: 1)]) + (final h) => h.range = RangeHeader(ranges: [Range(start: 1)]), ), ( Headers.referer, - (final h) => h.referer = Uri.parse('https://example.com') + (final h) => h.referer = Uri.parse('https://example.com'), ), ( Headers.referrerPolicy, - (final h) => h.referrerPolicy = ReferrerPolicyHeader.origin + (final h) => h.referrerPolicy = ReferrerPolicyHeader.origin, ), ( Headers.retryAfter, - (final h) => h.retryAfter = RetryAfterHeader(delay: 1) + (final h) => h.retryAfter = RetryAfterHeader(delay: 1), ), ( Headers.secFetchDest, - (final h) => h.secFetchDest = SecFetchDestHeader.audio + (final h) => h.secFetchDest = SecFetchDestHeader.audio, ), ( Headers.secFetchMode, - (final h) => h.secFetchMode = SecFetchModeHeader.cors + (final h) => h.secFetchMode = SecFetchModeHeader.cors, ), ( Headers.secFetchSite, - (final h) => h.secFetchSite = SecFetchSiteHeader.crossSite + (final h) => h.secFetchSite = SecFetchSiteHeader.crossSite, ), (Headers.server, (final h) => h.server = 'localhost'), ( Headers.setCookie, - (final h) => h.setCookie = SetCookieHeader(name: 'foo', value: 'bar') + (final h) => h.setCookie = SetCookieHeader(name: 'foo', value: 'bar'), ), ( Headers.strictTransportSecurity, - (final h) => h.strictTransportSecurity = - StrictTransportSecurityHeader(maxAge: 42) + (final h) => + h.strictTransportSecurity = StrictTransportSecurityHeader( + maxAge: 42, + ), ), (Headers.te, (final h) => h.te = TEHeader(encodings: [TeQuality('foo')])), (Headers.trailer, (final h) => h.trailer = ['foo']), ( Headers.transferEncoding, - (final h) => h.transferEncoding = - TransferEncodingHeader(encodings: [TransferEncoding.gzip]) + (final h) => + h.transferEncoding = TransferEncodingHeader( + encodings: [TransferEncoding.gzip], + ), ), ( Headers.upgrade, - (final h) => h.upgrade = - UpgradeHeader(protocols: [UpgradeProtocol(protocol: 'foo')]) + (final h) => + h.upgrade = UpgradeHeader( + protocols: [UpgradeProtocol(protocol: 'foo')], + ), ), (Headers.userAgent, (final h) => h.userAgent = 'null'), (Headers.vary, (final h) => h.vary = VaryHeader.wildcard()), (Headers.via, (final h) => h.via = ['foo']), ( Headers.wwwAuthenticate, - (final h) => h.wwwAuthenticate = AuthenticationHeader( - scheme: 'Bearer', - parameters: [const AuthenticationParameter('foo', 'bar')]) + (final h) => + h.wwwAuthenticate = AuthenticationHeader( + scheme: 'Bearer', + parameters: [const AuthenticationParameter('foo', 'bar')], + ), ), (Headers.xPoweredBy, (final h) => h.xPoweredBy = 'null'), ( Headers.forwarded, - (final h) => h.forwarded = ForwardedHeader([ + (final h) => + h.forwarded = ForwardedHeader([ ForwardedElement( - forwardedFor: const ForwardedIdentifier('192.1.0.1')) - ]) + forwardedFor: const ForwardedIdentifier('192.1.0.1'), + ), + ]), ), ( Headers.xForwardedFor, - (final h) => h.xForwardedFor = XForwardedForHeader(['192.1.0.1']) + (final h) => h.xForwardedFor = XForwardedForHeader(['192.1.0.1']), ), ].map((final r) => (accessor: r.$1, mutator: r.$2)), ); diff --git a/test/headers/headers_accessor_test.dart b/test/headers/headers_accessor_test.dart index 32f3174a..63d8823d 100644 --- a/test/headers/headers_accessor_test.dart +++ b/test/headers/headers_accessor_test.dart @@ -22,7 +22,9 @@ class Custom { } const _customClass = HeaderAccessor( - 'custom', HeaderCodec.single(Custom.parse, Custom.encode)); + 'custom', + HeaderCodec.single(Custom.parse, Custom.encode), +); extension on Headers { int? get anInt => _anInt[this](); @@ -37,16 +39,14 @@ extension on MutableHeaders { void main() { test('Given a correct header then single values are parsed correctly', () { final headers = Headers.fromMap({ - 'anInt': ['42'] + 'anInt': ['42'], }); expect(headers.anInt, isA()); expect(headers.anInt, 42); }); test('Given a correct header then multi values are parsed correctly', () { - final headers = Headers.fromMap({ - 'someStrings': 'foo bar'.split(' '), - }); + final headers = Headers.fromMap({'someStrings': 'foo bar'.split(' ')}); expect(headers.someStrings, isA>()); expect(headers.someStrings, ['foo', 'bar']); }); @@ -77,7 +77,7 @@ void main() { group('Given a Headers collection with an invalid entry', () { late final headers = Headers.fromMap({ - 'anInt': ['error'] + 'anInt': ['error'], }); late final header = _anInt[headers]; @@ -101,8 +101,7 @@ void main() { }); }); - test( - 'When setting a header on a mutable headers collection ' + test('When setting a header on a mutable headers collection ' 'then it succeeds', () { final headers = Headers.build((final mh) { expect(() => mh.anInt = 42, returnsNormally); @@ -110,8 +109,7 @@ void main() { expect(headers.anInt, 42); }); - test( - 'Given a mutable headers collection ' + test('Given a mutable headers collection ' 'When removing a header by setting to null ' 'then it succeeds', () { final headers = Headers.build((final mh) { @@ -126,8 +124,7 @@ void main() { expect(headers2, isNot(contains('anInt'))); }); - test( - 'Given a mutable headers collection ' + test('Given a mutable headers collection ' 'When removing a header using removeFrom ' 'then it succeeds', () { final headers = Headers.build((final mh) { @@ -142,8 +139,7 @@ void main() { }); // TODO: Should we try to prevent this scenario compile time? - test( - 'Given a immutable headers collection ' + test('Given a immutable headers collection ' 'When trying to set a header ' 'then it fails', () { final headers = Headers.build((final mh) { @@ -156,8 +152,7 @@ void main() { expect(() => header.set(null), throwsA(isA())); }); - test( - 'Given a header accessor ' + test('Given a header accessor ' 'when updating a value on a mutable headers collection ' 'then you can read the value immediately', () { Headers.build((final mh) { @@ -180,18 +175,18 @@ void main() { // a non-const decoder that increment local the local variable count // whenever called. This is not good practice, but useful in the test! accessor = HeaderAccessor( - 'tmp', - HeaderCodec.single((final s) { - ++count; - return int.parse(s); - }, encodeInt)); + 'tmp', + HeaderCodec.single((final s) { + ++count; + return int.parse(s); + }, encodeInt), + ); }); - test( - 'when reading the value from a headers collection twice ' + test('when reading the value from a headers collection twice ' 'then decode is only called once', () { final headers = Headers.fromMap({ - accessor.key: ['1202'] + accessor.key: ['1202'], }); expect(accessor[headers].value, 1202); @@ -201,15 +196,15 @@ void main() { expect(count, 1); }); - test( - 'when reading the value from a headers collection ' + test('when reading the value from a headers collection ' 'where the raw value is updated directly ' 'then decode is only called once per update', () { final headers = Headers.fromMap({ - accessor.key: ['1202'] + accessor.key: ['1202'], }); - final headers2 = - headers.transform((final mh) => mh[accessor.key] = ['42']); + final headers2 = headers.transform( + (final mh) => mh[accessor.key] = ['42'], + ); expect(accessor[headers].value, 1202); expect(count, 1); @@ -222,8 +217,7 @@ void main() { expect(count, 2); }); - test( - 'when reading the value from a headers collection ' + test('when reading the value from a headers collection ' 'where the encoded value is set via the accessor ' 'then decode is not needed at all', () { final headers = Headers.build((final mh) => accessor[mh].set(51)); @@ -233,13 +227,14 @@ void main() { }); test( - 'Given a custom class ' - 'then it is possible to setup header accessor for it with a custom encode', - () { - final c = Custom(); - final headers = Headers.build((final mh) => _customClass[mh].set(c)); - expect(headers[_customClass.key], ['foo']); - expect(_customClass[headers].raw, ['foo']); - expect(_customClass[headers].value, same(c)); - }); + 'Given a custom class ' + 'then it is possible to setup header accessor for it with a custom encode', + () { + final c = Custom(); + final headers = Headers.build((final mh) => _customClass[mh].set(c)); + expect(headers[_customClass.key], ['foo']); + expect(_customClass[headers].raw, ['foo']); + expect(_customClass[headers].value, same(c)); + }, + ); } diff --git a/test/headers/headers_constants_test.dart b/test/headers/headers_constants_test.dart index 631102e4..08690865 100644 --- a/test/headers/headers_constants_test.dart +++ b/test/headers/headers_constants_test.dart @@ -4,17 +4,19 @@ import 'package:test/test.dart'; void main() { group('Headers constants', () { test( - 'Headers.request contains request headers and excludes response only headers', - () { - expect(Headers.request, contains(Headers.host)); - expect(Headers.request, isNot(contains(Headers.etag))); - }); + 'Headers.request contains request headers and excludes response only headers', + () { + expect(Headers.request, contains(Headers.host)); + expect(Headers.request, isNot(contains(Headers.etag))); + }, + ); test( - 'Headers.response contains response headers and excludes request only headers', - () { - expect(Headers.response, contains(Headers.etag)); - expect(Headers.response, isNot(contains(Headers.host))); - }); + 'Headers.response contains response headers and excludes request only headers', + () { + expect(Headers.response, contains(Headers.etag)); + expect(Headers.response, isNot(contains(Headers.host))); + }, + ); }); } diff --git a/test/headers/headers_test_utils.dart b/test/headers/headers_test_utils.dart index 998c5ce1..bcb488a9 100644 --- a/test/headers/headers_test_utils.dart +++ b/test/headers/headers_test_utils.dart @@ -11,9 +11,7 @@ import 'package:test/test.dart'; class BadRequestException implements Exception { final String message; - BadRequestException( - this.message, - ); + BadRequestException(this.message); } /// Extension methods for RelicServer @@ -56,9 +54,7 @@ Future getServerRequestHeaders({ final statusCode = response.statusCode; if (statusCode == 400) { - throw BadRequestException( - response.body, - ); + throw BadRequestException(response.body); } if (statusCode != 200) { diff --git a/test/headers/mutable_headers_test.dart b/test/headers/mutable_headers_test.dart index 0dbafc3c..c542d01a 100644 --- a/test/headers/mutable_headers_test.dart +++ b/test/headers/mutable_headers_test.dart @@ -10,39 +10,34 @@ void main() { mutable['2'] = ['b']; mutable['3'] = ['c']; }); - test( - 'when assigning null to a key ' + test('when assigning null to a key ' 'then the value disappear', () { expect(mutable['1'], isNotNull); expect(mutable..remove('1'), { '2': ['b'], - '3': ['c'] + '3': ['c'], }); }); - test( - 'when removing a key ' + test('when removing a key ' 'then the value is removed', () { expect(() => mutable['2'] = null, returnsNormally); expect(mutable, { '1': ['a'], - '3': ['c'] + '3': ['c'], }); }); - test( - 'when accessing keys ' + test('when accessing keys ' 'then all keys are returned', () { expect(mutable.keys, ['1', '2', '3']); }); - test( - 'when clearing ' + test('when clearing ' 'then all values is removed', () { expect(() => mutable.clear(), returnsNormally); expect(mutable, MutableHeaders()); }); }); - test( - 'When assigning a value during Headers.build ' + test('When assigning a value during Headers.build ' 'then its present in the returned headers collection', () { final headers = Headers.build((final mh) { mh['foo'] = ['bar']; diff --git a/test/headers/typed/accept_encoding_header_test.dart b/test/headers/typed/accept_encoding_header_test.dart index 8cc2d090..cee83a23 100644 --- a/test/headers/typed/accept_encoding_header_test.dart +++ b/test/headers/typed/accept_encoding_header_test.dart @@ -37,27 +37,24 @@ void main() { }, ); - test( - 'when an Accept-Encoding header with invalid quality values is passed ' - 'then the server responds with a bad request including a message that ' - 'states the quality value is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'accept-encoding': 'gzip;q=abc'}, - touchHeaders: (final h) => h.acceptEncoding, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid quality value'), - ), + test('when an Accept-Encoding header with invalid quality values is passed ' + 'then the server responds with a bad request including a message that ' + 'states the quality value is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + headers: {'accept-encoding': 'gzip;q=abc'}, + touchHeaders: (final h) => h.acceptEncoding, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid quality value'), ), - ); - }, - ); + ), + ); + }); test( 'when an Accept-Encoding header with wildcard (*) and other encodings is ' @@ -81,42 +78,36 @@ void main() { }, ); - test( - 'when an Accept-Encoding header with empty encoding is passed then ' - 'the server responds with a bad request including a message that ' - 'states the encoding is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - headers: {'accept-encoding': ';q=0.5'}, - touchHeaders: (final h) => h.acceptEncoding, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid encoding'), - ), - ), - ); - }, - ); - - test( - 'when an Accept-Encoding header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + test('when an Accept-Encoding header with empty encoding is passed then ' + 'the server responds with a bad request including a message that ' + 'states the encoding is invalid', () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, headers: {'accept-encoding': ';q=0.5'}, - ); + touchHeaders: (final h) => h.acceptEncoding, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid encoding'), + ), + ), + ); + }); - expect(headers, isNotNull); - }, - ); + test('when an Accept-Encoding header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'accept-encoding': ';q=0.5'}, + ); + + expect(headers, isNotNull); + }); test( 'when an Accept-Encoding header is passed then it should parse the encoding correctly', @@ -136,43 +127,33 @@ void main() { }, ); - test( - 'when an Accept-Encoding header is passed without quality then the ' - 'default quality value should be set', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'accept-encoding': 'gzip'}, - touchHeaders: (final h) => h.acceptEncoding, - ); + test('when an Accept-Encoding header is passed without quality then the ' + 'default quality value should be set', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'accept-encoding': 'gzip'}, + touchHeaders: (final h) => h.acceptEncoding, + ); - expect( - headers.acceptEncoding?.encodings - .map((final e) => e.quality) - .toList(), - equals([1.0]), - ); - }, - ); + expect( + headers.acceptEncoding?.encodings.map((final e) => e.quality).toList(), + equals([1.0]), + ); + }); - test( - 'when an Accept-Encoding header is passed with quality then the ' - 'quality value should be set', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'accept-encoding': 'gzip;q=0.5'}, - touchHeaders: (final h) => h.acceptEncoding, - ); + test('when an Accept-Encoding header is passed with quality then the ' + 'quality value should be set', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'accept-encoding': 'gzip;q=0.5'}, + touchHeaders: (final h) => h.acceptEncoding, + ); - expect( - headers.acceptEncoding?.encodings - .map((final e) => e.quality) - .toList(), - equals([0.5]), - ); - }, - ); + expect( + headers.acceptEncoding?.encodings.map((final e) => e.quality).toList(), + equals([0.5]), + ); + }); test( 'when a mixed case Accept-Encoding header is passed then it should parse ' @@ -258,23 +239,20 @@ void main() { ); group('when multiple Accept-Encoding headers are passed', () { - test( - 'then they should parse the encodings correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'accept-encoding': 'gzip, deflate, br'}, - touchHeaders: (final h) => h.acceptEncoding, - ); + test('then they should parse the encodings correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'accept-encoding': 'gzip, deflate, br'}, + touchHeaders: (final h) => h.acceptEncoding, + ); - expect( - headers.acceptEncoding?.encodings - .map((final e) => e.encoding) - .toList(), - equals(['gzip', 'deflate', 'br']), - ); - }, - ); + expect( + headers.acceptEncoding?.encodings + .map((final e) => e.encoding) + .toList(), + equals(['gzip', 'deflate', 'br']), + ); + }); test( 'with quality values then they should parse the encodings correctly', @@ -312,24 +290,21 @@ void main() { }, ); - test( - 'with duplicated values then it should remove duplicates and parse ' - 'the encodings correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - headers: {'accept-encoding': 'gzip, gzip, deflate, br'}, - touchHeaders: (final h) => h.acceptEncoding, - ); + test('with duplicated values then it should remove duplicates and parse ' + 'the encodings correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + headers: {'accept-encoding': 'gzip, gzip, deflate, br'}, + touchHeaders: (final h) => h.acceptEncoding, + ); - expect( - headers.acceptEncoding?.encodings - .map((final e) => e.encoding) - .toList(), - equals(['gzip', 'deflate', 'br']), - ); - }, - ); + expect( + headers.acceptEncoding?.encodings + .map((final e) => e.encoding) + .toList(), + equals(['gzip', 'deflate', 'br']), + ); + }); test( 'with extra whitespace then it should parse the encodings correctly', @@ -361,36 +336,32 @@ void main() { tearDown(() => server.close()); group('when an invalid Accept-Encoding header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'accept-encoding': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'accept-encoding': ''}, + ); - expect(Headers.acceptEncoding[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.acceptEncoding, throwsInvalidHeader); - }, - ); + expect(Headers.acceptEncoding[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.acceptEncoding, throwsInvalidHeader); + }); }); - group('when Accept-Encoding headers with invalid quality values are passed', - () { - test( - 'then it should return null', - () async { + group( + 'when Accept-Encoding headers with invalid quality values are passed', + () { + test('then it should return null', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'accept-encoding': 'gzip;q=abc, deflate, br'}, ); expect(Headers.acceptEncoding[headers].valueOrNullIfInvalid, isNull); expect(() => headers.acceptEncoding, throwsInvalidHeader); - }, - ); - }); + }); + }, + ); }); } diff --git a/test/headers/typed/accept_header_test.dart b/test/headers/typed/accept_header_test.dart index 6f662aea..7c73e6a5 100644 --- a/test/headers/typed/accept_header_test.dart +++ b/test/headers/typed/accept_header_test.dart @@ -26,11 +26,13 @@ void main() { touchHeaders: (final h) => h.accept, headers: {'accept': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -46,29 +48,28 @@ void main() { touchHeaders: (final h) => h.accept, headers: {'accept': 'text/html;q=abc'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid quality value'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid quality value'), + ), + ), ); }, ); - test( - 'when an Accept header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'accept': 'text/html;q=abc'}, - ); + test('when an Accept header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'accept': 'text/html;q=abc'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid Accept header is passed then it should parse the media types correctly', @@ -86,21 +87,18 @@ void main() { }, ); - test( - 'when a valid Accept header with no quality value is passed then the ' - 'quality value should set to default of 1.0', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.accept, - headers: {'accept': 'text/html'}, - ); + test('when a valid Accept header with no quality value is passed then the ' + 'quality value should set to default of 1.0', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.accept, + headers: {'accept': 'text/html'}, + ); - final mediaRanges = headers.accept?.mediaRanges; - expect(mediaRanges?.length, equals(1)); - expect(mediaRanges?[0].quality, equals(1.0)); - }, - ); + final mediaRanges = headers.accept?.mediaRanges; + expect(mediaRanges?.length, equals(1)); + expect(mediaRanges?[0].quality, equals(1.0)); + }); test( 'when a valid Accept header with quality value is passed then it should parse the quality value correctly', @@ -156,14 +154,16 @@ void main() { server: server, touchHeaders: (final h) => h.accept, headers: { - 'accept': 'text/html;q=test, application/json;q=abc, */*;q=0.5' + 'accept': 'text/html;q=test, application/json;q=abc, */*;q=0.5', }, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid quality value'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid quality value'), + ), + ), ); }, ); @@ -174,7 +174,7 @@ void main() { server: server, touchHeaders: (final h) => h.accept, headers: { - 'accept': 'text/html;q=0.8, application/json;q=0.9, */*;q=0.5' + 'accept': 'text/html;q=0.8, application/json;q=0.9, */*;q=0.5', }, ); @@ -196,7 +196,7 @@ void main() { server: server, touchHeaders: (final h) => h.accept, headers: { - 'accept': 'text/html;q=0.8, application/json;q=0.9, */*;q=0.5' + 'accept': 'text/html;q=0.8, application/json;q=0.9, */*;q=0.5', }, ); @@ -220,19 +220,16 @@ void main() { tearDown(() => server.close()); group('when an Accept header with invalid quality value is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'accept': 'text/html;q=abc'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'accept': 'text/html;q=abc'}, + ); - expect(Headers.accept[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.accept, throwsInvalidHeader); - }, - ); + expect(Headers.accept[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.accept, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/accept_language_test.dart b/test/headers/typed/accept_language_test.dart index 48cd09db..fca24157 100644 --- a/test/headers/typed/accept_language_test.dart +++ b/test/headers/typed/accept_language_test.dart @@ -7,424 +7,385 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given an Accept-Language header with validation', - () { - late RelicServer server; - - setUp(() async { - server = await createServer(); - }); - - tearDown(() => server.close()); - - test( - 'when an empty Accept-Language header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': ''}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), + group('Given an Accept-Language header with validation', () { + late RelicServer server; + + setUp(() async { + server = await createServer(); + }); + + tearDown(() => server.close()); + + test( + 'when an empty Accept-Language header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); + ), + ); + }, + ); - test( - 'when an Accept-Language header with invalid quality values is passed ' + test('when an Accept-Language header with invalid quality values is passed ' 'then the server responds with a bad request including a message that ' - 'states the quality value is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'en;q=abc'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid quality value'), - ), - ), - ); - }, + 'states the quality value is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': 'en;q=abc'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid quality value'), + ), + ), ); - - test( - 'when an Accept-Language header with wildcard (*) and other languages is ' - 'passed then the server responds with a bad request including a message ' - 'that states the wildcard (*) cannot be used with other values', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': '*, en'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Wildcard (*) cannot be used with other values'), - ), + }); + + test( + 'when an Accept-Language header with wildcard (*) and other languages is ' + 'passed then the server responds with a bad request including a message ' + 'that states the wildcard (*) cannot be used with other values', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': '*, en'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Wildcard (*) cannot be used with other values'), ), - ); - }, - ); + ), + ); + }, + ); - test( - 'when an Accept-Language header with empty language is passed then ' + test('when an Accept-Language header with empty language is passed then ' 'the server responds with a bad request including a message that ' - 'states the language is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': ';q=0.5'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid language'), - ), - ), - ); - }, + 'states the language is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': ';q=0.5'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid language'), + ), + ), ); + }); - test( - 'when an Accept-Language header with an invalid value is passed ' + test('when an Accept-Language header with an invalid value is passed ' 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'accept-language': ';q=0.5'}, - ); + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'accept-language': ';q=0.5'}, + ); - expect(headers, isNotNull); - }, + expect(headers, isNotNull); + }); + + test('when an Accept-Language header is passed then it should parse the ' + 'language correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': 'en'}, ); + expect( + headers.acceptLanguage?.languages.map((final e) => e.language).toList(), + equals(['en']), + ); + }); + + test('when an Accept-Language header is passed without quality then the ' + 'default quality value should be set', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': 'en'}, + ); + + expect( + headers.acceptLanguage?.languages.map((final e) => e.quality).toList(), + equals([1.0]), + ); + }); + + test( + 'when a mixed case Accept-Language header is passed then it should parse ' + 'the language correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': 'En'}, + ); + + expect( + headers.acceptLanguage?.languages + .map((final e) => e.language) + .toList(), + equals(['en']), + ); + }, + ); + + test( + 'when no Accept-Language header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {}, + ); + + expect(headers.acceptLanguage, isNull); + }, + ); + + test( + 'when an Accept-Language header with wildcard (*) is passed then it should parse correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': '*'}, + ); + + expect(headers.acceptLanguage?.isWildcard, isTrue); + expect(headers.acceptLanguage?.languages, isEmpty); + }, + ); + + test( + 'when an Accept-Language header with wildcard (*) and quality value is passed ' + 'then it should parse the encoding correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': '*;q=0.5'}, + ); + + expect( + headers.acceptLanguage?.languages + .map((final e) => e.language) + .toList(), + equals(['*']), + ); + expect( + headers.acceptLanguage?.languages + .map((final e) => e.quality) + .toList(), + equals([0.5]), + ); + }, + ); + + group('when multiple Accept-Language headers are passed', () { + test('then they should parse the languages correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': 'en, fr, de'}, + ); + + expect( + headers.acceptLanguage?.languages + .map((final e) => e.language) + .toList(), + equals(['en', 'fr', 'de']), + ); + }); + + test('then they should parse the qualities correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.acceptLanguage, + headers: {'accept-language': 'en, fr, de'}, + ); + + expect( + headers.acceptLanguage?.languages + .map((final e) => e.quality) + .toList(), + equals([1.0, 1.0, 1.0]), + ); + }); + test( - 'when an Accept-Language header is passed then it should parse the ' - 'language correctly', + 'with quality values then they should parse the languages correctly', () async { final headers = await getServerRequestHeaders( server: server, touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'en'}, + headers: {'accept-language': 'en;q=1.0, fr;q=0.5, de;q=0.8'}, ); expect( headers.acceptLanguage?.languages .map((final e) => e.language) .toList(), - equals(['en']), + equals(['en', 'fr', 'de']), ); }, ); test( - 'when an Accept-Language header is passed without quality then the ' - 'default quality value should be set', + 'with quality values then they should parse the qualities correctly', () async { final headers = await getServerRequestHeaders( server: server, touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'en'}, + headers: {'accept-language': 'en;q=1.0, fr;q=0.5, de;q=0.8'}, ); expect( headers.acceptLanguage?.languages .map((final e) => e.quality) .toList(), - equals([1.0]), + equals([1.0, 0.5, 0.8]), ); }, ); test( - 'when a mixed case Accept-Language header is passed then it should parse ' - 'the language correctly', + 'with duplicated values then it should remove duplicates and parse the languages correctly', () async { final headers = await getServerRequestHeaders( server: server, touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'En'}, + headers: {'accept-language': 'en, en, fr, de'}, ); expect( headers.acceptLanguage?.languages .map((final e) => e.language) .toList(), - equals(['en']), + equals(['en', 'fr', 'de']), ); }, ); test( - 'when no Accept-Language header is passed then it should return null', + 'with duplicated values then it should remove duplicates and parse the qualities correctly', () async { final headers = await getServerRequestHeaders( server: server, touchHeaders: (final h) => h.acceptLanguage, - headers: {}, + headers: {'accept-language': 'en, en, fr, de'}, ); - expect(headers.acceptLanguage, isNull); + expect( + headers.acceptLanguage?.languages + .map((final e) => e.quality) + .toList(), + equals([1.0, 1.0, 1.0]), + ); }, ); test( - 'when an Accept-Language header with wildcard (*) is passed then it should parse correctly', + 'with extra whitespace then it should parse the languages correctly', () async { final headers = await getServerRequestHeaders( server: server, touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': '*'}, + headers: {'accept-language': ' en , fr , de '}, ); - expect(headers.acceptLanguage?.isWildcard, isTrue); - expect(headers.acceptLanguage?.languages, isEmpty); + expect( + headers.acceptLanguage?.languages + .map((final e) => e.language) + .toList(), + equals(['en', 'fr', 'de']), + ); }, ); test( - 'when an Accept-Language header with wildcard (*) and quality value is passed ' - 'then it should parse the encoding correctly', + 'with extra whitespace then it should parse the qualities correctly', () async { final headers = await getServerRequestHeaders( server: server, touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': '*;q=0.5'}, + headers: {'accept-language': ' en , fr , de '}, ); - expect( - headers.acceptLanguage?.languages - .map((final e) => e.language) - .toList(), - equals(['*']), - ); expect( headers.acceptLanguage?.languages .map((final e) => e.quality) .toList(), - equals([0.5]), + equals([1.0, 1.0, 1.0]), ); }, ); + }); + }); - group('when multiple Accept-Language headers are passed', () { - test( - 'then they should parse the languages correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'en, fr, de'}, - ); - - expect( - headers.acceptLanguage?.languages - .map((final e) => e.language) - .toList(), - equals(['en', 'fr', 'de']), - ); - }, - ); - - test( - 'then they should parse the qualities correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'en, fr, de'}, - ); - - expect( - headers.acceptLanguage?.languages - .map((final e) => e.quality) - .toList(), - equals([1.0, 1.0, 1.0]), - ); - }, - ); + group('Given an Accept-Language header without validation', () { + late RelicServer server; - test( - 'with quality values then they should parse the languages correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'en;q=1.0, fr;q=0.5, de;q=0.8'}, - ); - - expect( - headers.acceptLanguage?.languages - .map((final e) => e.language) - .toList(), - equals(['en', 'fr', 'de']), - ); - }, - ); + setUp(() async { + server = await createServer(); + }); - test( - 'with quality values then they should parse the qualities correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'en;q=1.0, fr;q=0.5, de;q=0.8'}, - ); - - expect( - headers.acceptLanguage?.languages - .map((final e) => e.quality) - .toList(), - equals([1.0, 0.5, 0.8]), - ); - }, - ); + tearDown(() => server.close()); - test( - 'with duplicated values then it should remove duplicates and parse the languages correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'en, en, fr, de'}, - ); - - expect( - headers.acceptLanguage?.languages - .map((final e) => e.language) - .toList(), - equals(['en', 'fr', 'de']), - ); - }, + group('when an invalid Accept-Language header is passed', () { + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'accept-language': ''}, ); - test( - 'with duplicated values then it should remove duplicates and parse the qualities correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': 'en, en, fr, de'}, - ); - - expect( - headers.acceptLanguage?.languages - .map((final e) => e.quality) - .toList(), - equals([1.0, 1.0, 1.0]), - ); - }, - ); - - test( - 'with extra whitespace then it should parse the languages correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': ' en , fr , de '}, - ); - - expect( - headers.acceptLanguage?.languages - .map((final e) => e.language) - .toList(), - equals(['en', 'fr', 'de']), - ); - }, - ); - - test( - 'with extra whitespace then it should parse the qualities correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.acceptLanguage, - headers: {'accept-language': ' en , fr , de '}, - ); - - expect( - headers.acceptLanguage?.languages - .map((final e) => e.quality) - .toList(), - equals([1.0, 1.0, 1.0]), - ); - }, - ); + expect(Headers.acceptLanguage[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.acceptLanguage, throwsInvalidHeader); }); - }, - ); + }); - group( - 'Given an Accept-Language header without validation', - () { - late RelicServer server; - - setUp(() async { - server = await createServer(); - }); - - tearDown(() => server.close()); - - group('when an invalid Accept-Language header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'accept-language': ''}, - ); - - expect( - Headers.acceptLanguage[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.acceptLanguage, throwsInvalidHeader); - }, - ); - }); - - group( - 'when Accept-Language headers with invalid quality values are passed', - () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'accept-language': 'en;q=abc, fr, de'}, - ); - - expect( - Headers.acceptLanguage[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.acceptLanguage, throwsInvalidHeader); - }, + group( + 'when Accept-Language headers with invalid quality values are passed', + () { + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'accept-language': 'en;q=abc, fr, de'}, ); - }, - ); - }, - ); + + expect(Headers.acceptLanguage[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.acceptLanguage, throwsInvalidHeader); + }); + }, + ); + }); } diff --git a/test/headers/typed/accept_ranges_header_test.dart b/test/headers/typed/accept_ranges_header_test.dart index 11cb7a94..28aca27d 100644 --- a/test/headers/typed/accept_ranges_header_test.dart +++ b/test/headers/typed/accept_ranges_header_test.dart @@ -26,29 +26,28 @@ void main() { touchHeaders: (final h) => h.acceptRanges, headers: {'accept-ranges': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); - test( - 'when an Accept-Ranges header with an empty value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'accept-ranges': ''}, - ); + test('when an Accept-Ranges header with an empty value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'accept-ranges': ''}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid Accept-Ranges header is passed then it should parse the range unit correctly', @@ -102,19 +101,16 @@ void main() { tearDown(() => server.close()); group('when an empty Accept-Ranges header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'accept-ranges': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'accept-ranges': ''}, + ); - expect(Headers.acceptRanges[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.acceptRanges, throwsInvalidHeader); - }, - ); + expect(Headers.acceptRanges[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.acceptRanges, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/access_control_allow_headers_header_test.dart b/test/headers/typed/access_control_allow_headers_header_test.dart index 882cb3ef..80ee8fdd 100644 --- a/test/headers/typed/access_control_allow_headers_header_test.dart +++ b/test/headers/typed/access_control_allow_headers_header_test.dart @@ -16,25 +16,24 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty Access-Control-Allow-Headers header is passed then ' - 'the server should respond with a bad request including a message ' - 'that states the value cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.accessControlAllowHeaders, - headers: {'access-control-allow-headers': ''}, - ), - throwsA(isA().having( + test('when an empty Access-Control-Allow-Headers header is passed then ' + 'the server should respond with a bad request including a message ' + 'that states the value cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.accessControlAllowHeaders, + headers: {'access-control-allow-headers': ''}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Value cannot be empty'), - )), - ); - }, - ); + ), + ), + ); + }); test( 'when an Access-Control-Allow-Headers header with a wildcard (*) ' @@ -47,11 +46,13 @@ void main() { touchHeaders: (final h) => h.accessControlAllowHeaders, headers: {'access-control-allow-headers': '*, Content-Type'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Wildcard (*) cannot be used with other values'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Wildcard (*) cannot be used with other values'), + ), + ), ); }, ); @@ -63,7 +64,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'access-control-allow-headers': '*, Content-Type'}, ); @@ -76,9 +77,9 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { - 'access-control-allow-headers': 'Content-Type, X-Custom-Header' + 'access-control-allow-headers': 'Content-Type, X-Custom-Header', }, ); @@ -131,7 +132,7 @@ void main() { test('then it should return null', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {}, ); expect(headers.accessControlAllowHeaders, isNull); diff --git a/test/headers/typed/access_control_allow_methods_header_test.dart b/test/headers/typed/access_control_allow_methods_header_test.dart index 47bb62dd..9966ee3f 100644 --- a/test/headers/typed/access_control_allow_methods_header_test.dart +++ b/test/headers/typed/access_control_allow_methods_header_test.dart @@ -26,11 +26,13 @@ void main() { touchHeaders: (final h) => h.accessControlAllowMethods, headers: {'access-control-allow-methods': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -47,11 +49,13 @@ void main() { touchHeaders: (final h) => h.accessControlAllowMethods, headers: {'access-control-allow-methods': 'GET, *'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Wildcard (*) cannot be used with other values'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Wildcard (*) cannot be used with other values'), + ), + ), ); }, ); @@ -63,7 +67,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'access-control-allow-methods': 'CUSTOM'}, ); @@ -145,17 +149,14 @@ void main() { tearDown(() => server.close()); group('when an empty Access-Control-Allow-Methods header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {}, - ); - expect(headers.accessControlAllowMethods, isNull); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {}, + ); + expect(headers.accessControlAllowMethods, isNull); + }); }); }); } diff --git a/test/headers/typed/access_control_allow_origin_header_test.dart b/test/headers/typed/access_control_allow_origin_header_test.dart index f6470fd7..9abbbb45 100644 --- a/test/headers/typed/access_control_allow_origin_header_test.dart +++ b/test/headers/typed/access_control_allow_origin_header_test.dart @@ -70,7 +70,7 @@ void main() { server: server, touchHeaders: (final h) => h.accessControlAllowOrigin, headers: { - 'access-control-allow-origin': 'https://example.com:test' + 'access-control-allow-origin': 'https://example.com:test', }, ), throwsA( @@ -91,7 +91,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'access-control-allow-origin': 'https://example.com:test'}, ); @@ -126,10 +126,7 @@ void main() { headers: {'access-control-allow-origin': 'https://example.com:8080'}, ); - expect( - headers.accessControlAllowOrigin?.origin?.port, - equals(8080), - ); + expect(headers.accessControlAllowOrigin?.origin?.port, equals(8080)); }, ); @@ -189,20 +186,19 @@ void main() { tearDown(() => server.close()); group('When an invalid Access-Control-Allow-Origin header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'access-control-allow-origin': 'ht!tp://invalid-url'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'access-control-allow-origin': 'ht!tp://invalid-url'}, + ); - expect(Headers.accessControlAllowOrigin[headers].valueOrNullIfInvalid, - isNull); - expect(() => headers.accessControlAllowOrigin, throwsInvalidHeader); - }, - ); + expect( + Headers.accessControlAllowOrigin[headers].valueOrNullIfInvalid, + isNull, + ); + expect(() => headers.accessControlAllowOrigin, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/access_control_expose_headers_header_test.dart b/test/headers/typed/access_control_expose_headers_header_test.dart index b2df049b..eed35916 100644 --- a/test/headers/typed/access_control_expose_headers_header_test.dart +++ b/test/headers/typed/access_control_expose_headers_header_test.dart @@ -68,7 +68,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'access-control-expose-headers': '*, X-Custom-Header'}, ); @@ -121,43 +121,37 @@ void main() { ); group('when multiple Access-Control-Expose-Headers headers are passed', () { - test( - 'then they should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: { - 'access-control-expose-headers': - 'X-Custom-Header, X-Another-Header' - }, - ); - - expect( - headers.accessControlExposeHeaders?.headers, - equals(['X-Custom-Header', 'X-Another-Header']), - ); - }, - ); - - test( - 'with extra whitespace then they should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: { - 'access-control-expose-headers': - ' X-Custom-Header , X-Another-Header ' - }, - ); - - expect( - headers.accessControlExposeHeaders?.headers, - equals(['X-Custom-Header', 'X-Another-Header']), - ); - }, - ); + test('then they should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: { + 'access-control-expose-headers': + 'X-Custom-Header, X-Another-Header', + }, + ); + + expect( + headers.accessControlExposeHeaders?.headers, + equals(['X-Custom-Header', 'X-Another-Header']), + ); + }); + + test('with extra whitespace then they should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: { + 'access-control-expose-headers': + ' X-Custom-Header , X-Another-Header ', + }, + ); + + expect( + headers.accessControlExposeHeaders?.headers, + equals(['X-Custom-Header', 'X-Another-Header']), + ); + }); }); }); @@ -171,21 +165,19 @@ void main() { tearDown(() => server.close()); group('when an invalid Access-Control-Expose-Headers header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'access-control-expose-headers': ''}, - ); - - expect( - Headers.accessControlExposeHeaders[headers].valueOrNullIfInvalid, - isNull); - expect(() => headers.accessControlExposeHeaders, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'access-control-expose-headers': ''}, + ); + + expect( + Headers.accessControlExposeHeaders[headers].valueOrNullIfInvalid, + isNull, + ); + expect(() => headers.accessControlExposeHeaders, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/access_control_request_method_header_test.dart b/test/headers/typed/access_control_request_method_header_test.dart index 68b9a56a..23e8e764 100644 --- a/test/headers/typed/access_control_request_method_header_test.dart +++ b/test/headers/typed/access_control_request_method_header_test.dart @@ -38,27 +38,24 @@ void main() { }, ); - test( - 'when an invalid method is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.accessControlRequestMethod, - headers: {'access-control-request-method': 'CUSTOM'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - ), + test('when an invalid method is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.accessControlRequestMethod, + headers: {'access-control-request-method': 'CUSTOM'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid value'), ), - ); - }, - ); + ), + ); + }); test( 'when an Access-Control-Request-Method header with an invalid value is ' @@ -67,7 +64,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'access-control-request-method': 'TEST'}, ); @@ -75,22 +72,16 @@ void main() { }, ); - test( - 'when a valid Access-Control-Request-Method header is passed then it ' - 'should parse the method correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.accessControlRequestMethod, - headers: {'access-control-request-method': 'POST'}, - ); + test('when a valid Access-Control-Request-Method header is passed then it ' + 'should parse the method correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.accessControlRequestMethod, + headers: {'access-control-request-method': 'POST'}, + ); - expect( - headers.accessControlRequestMethod, - equals(Method.post), - ); - }, - ); + expect(headers.accessControlRequestMethod, equals(Method.post)); + }); test( 'when an Access-Control-Request-Method header with extra whitespace is ' @@ -131,21 +122,19 @@ void main() { tearDown(() => server.close()); group('when an empty Access-Control-Request-Method header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'access-control-request-method': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'access-control-request-method': ''}, + ); - expect( - Headers.accessControlRequestMethod[headers].valueOrNullIfInvalid, - isNull); - expect(() => headers.accessControlRequestMethod, throwsInvalidHeader); - }, - ); + expect( + Headers.accessControlRequestMethod[headers].valueOrNullIfInvalid, + isNull, + ); + expect(() => headers.accessControlRequestMethod, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/authorization_header_test.dart b/test/headers/typed/authorization_header_test.dart index e97fe2fd..d83fc2b0 100644 --- a/test/headers/typed/authorization_header_test.dart +++ b/test/headers/typed/authorization_header_test.dart @@ -41,20 +41,17 @@ void main() { }, ); - test( - 'when a Authorization header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'authorization': 'invalid-authorization-format'}, - ); + test('when a Authorization header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'authorization': 'invalid-authorization-format'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when no Authorization header is passed then it should default to null', @@ -134,25 +131,22 @@ void main() { }, ); - test( - 'when a Basic token is passed then it should parse the credentials ' - 'correctly', - () async { - final credentials = base64Encode(utf8.encode('user:pass:word')); - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Basic $credentials'}, - ); + test('when a Basic token is passed then it should parse the credentials ' + 'correctly', () async { + final credentials = base64Encode(utf8.encode('user:pass:word')); + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Basic $credentials'}, + ); - expect( - headers.authorization, - isA() - .having((final auth) => auth.username, 'username', 'user') - .having((final auth) => auth.password, 'password', 'pass:word'), - ); - }, - ); + expect( + headers.authorization, + isA() + .having((final auth) => auth.username, 'username', 'user') + .having((final auth) => auth.password, 'password', 'pass:word'), + ); + }); }); group('and a Digest Authorization header', () { @@ -178,270 +172,237 @@ void main() { ); group('when a Digest token is passed with missing', () { - test( - '"username" then the server responds with a bad request including ' - 'a message that states the username is required', - () async { - const digestValue = - 'missingUsername="user", realm="realm", nonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Username is required and cannot be empty'), - ), - ), - ); - }, - ); - test( - '"realm" then the server responds with a bad request including ' - 'a message that states the realm is required', - () async { - const digestValue = - 'username="user", missingRealm="realm", nonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Realm is required and cannot be empty'), - ), - ), - ); - }, - ); + test('"username" then the server responds with a bad request including ' + 'a message that states the username is required', () async { + const digestValue = + 'missingUsername="user", realm="realm", nonce="nonce", uri="/", response="response"'; - test( - '"nonce" then the server responds with a bad request including ' - 'a message that states the nonce is required', - () async { - const digestValue = - 'username="user", realm="realm", missingNonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Nonce is required and cannot be empty'), - ), + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Username is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); + test('"realm" then the server responds with a bad request including ' + 'a message that states the realm is required', () async { + const digestValue = + 'username="user", missingRealm="realm", nonce="nonce", uri="/", response="response"'; - test( - '"uri" then the server responds with a bad request including ' - 'a message that states the uri is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="nonce", missingUri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Realm is required and cannot be empty'), ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('URI is required and cannot be empty'), - ), + ), + ); + }); + + test('"nonce" then the server responds with a bad request including ' + 'a message that states the nonce is required', () async { + const digestValue = + 'username="user", realm="realm", missingNonce="nonce", uri="/", response="response"'; + + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Nonce is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); + + test('"uri" then the server responds with a bad request including ' + 'a message that states the uri is required', () async { + const digestValue = + 'username="user", realm="realm", nonce="nonce", missingUri="/", response="response"'; - test( - '"response" then the server responds with a bad request including ' - 'a message that states the response is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="nonce", uri="/", missingResponse="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('URI is required and cannot be empty'), ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Response is required and cannot be empty'), - ), + ), + ); + }); + + test('"response" then the server responds with a bad request including ' + 'a message that states the response is required', () async { + const digestValue = + 'username="user", realm="realm", nonce="nonce", uri="/", missingResponse="response"'; + + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Response is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); }); group('when a Digest token is passed with empty', () { - test( - '"username" then the server responds with a bad request including ' - 'a message that states the username is required', - () async { - const digestValue = - 'username="", realm="realm", nonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Username is required and cannot be empty'), - ), - ), - ); - }, - ); - test( - '"realm" then the server responds with a bad request including ' - 'a message that states the realm is required', - () async { - const digestValue = - 'username="user", realm="", nonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Realm is required and cannot be empty'), - ), - ), - ); - }, - ); + test('"username" then the server responds with a bad request including ' + 'a message that states the username is required', () async { + const digestValue = + 'username="", realm="realm", nonce="nonce", uri="/", response="response"'; - test( - '"nonce" then the server responds with a bad request including ' - 'a message that states the nonce is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Nonce is required and cannot be empty'), - ), + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Username is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); + test('"realm" then the server responds with a bad request including ' + 'a message that states the realm is required', () async { + const digestValue = + 'username="user", realm="", nonce="nonce", uri="/", response="response"'; - test( - '"uri" then the server responds with a bad request including ' - 'a message that states the uri is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="nonce", uri="", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('URI is required and cannot be empty'), - ), + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Realm is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); - test( - '"response" then the server responds with a bad request including ' - 'a message that states the response is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="nonce", uri="/", response=""'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Response is required and cannot be empty'), - ), + test('"nonce" then the server responds with a bad request including ' + 'a message that states the nonce is required', () async { + const digestValue = + 'username="user", realm="realm", nonce="", uri="/", response="response"'; + + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Nonce is required and cannot be empty'), ), - ); - }, - ); - }); + ), + ); + }); - test( - 'when a Digest token is passed with all required parameters then it ' - 'should parse the credentials correctly', - () async { + test('"uri" then the server responds with a bad request including ' + 'a message that states the uri is required', () async { const digestValue = - 'username="user", realm="realm", nonce="nonce", uri="/", response="response"'; - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.authorization, - headers: {'authorization': 'Digest $digestValue'}, + 'username="user", realm="realm", nonce="nonce", uri="", response="response"'; + + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('URI is required and cannot be empty'), + ), + ), ); + }); + + test('"response" then the server responds with a bad request including ' + 'a message that states the response is required', () async { + const digestValue = + 'username="user", realm="realm", nonce="nonce", uri="/", response=""'; expect( - headers.authorization, - isA() - .having((final auth) => auth.username, 'username', 'user') - .having((final auth) => auth.realm, 'realm', 'realm') - .having((final auth) => auth.nonce, 'nonce', 'nonce') - .having((final auth) => auth.uri, 'uri', '/') - .having( - (final auth) => auth.response, 'response', 'response')); - }, - ); + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Response is required and cannot be empty'), + ), + ), + ); + }); + }); + + test('when a Digest token is passed with all required parameters then it ' + 'should parse the credentials correctly', () async { + const digestValue = + 'username="user", realm="realm", nonce="nonce", uri="/", response="response"'; + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.authorization, + headers: {'authorization': 'Digest $digestValue'}, + ); + + expect( + headers.authorization, + isA() + .having((final auth) => auth.username, 'username', 'user') + .having((final auth) => auth.realm, 'realm', 'realm') + .having((final auth) => auth.nonce, 'nonce', 'nonce') + .having((final auth) => auth.uri, 'uri', '/') + .having((final auth) => auth.response, 'response', 'response'), + ); + }); test( 'when a Digest token is passed then it should parse the credentials correctly', @@ -483,35 +444,29 @@ void main() { tearDown(() => server.close()); group('when an empty Authorization header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'authorization': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'authorization': ''}, + ); - expect(Headers.authorization[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.authorization, throwsInvalidHeader); - }, - ); + expect(Headers.authorization[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.authorization, throwsInvalidHeader); + }); }); group('when an invalid Authorization header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'authorization': 'InvalidFormat'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'authorization': 'InvalidFormat'}, + ); - expect(Headers.authorization[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.authorization, throwsInvalidHeader); - }, - ); + expect(Headers.authorization[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.authorization, throwsInvalidHeader); + }); }); }); @@ -528,12 +483,10 @@ void main() { (final v) { final matcher = v.$3; singleTest( - 'then it ${matcher.describe(StringDescription())}', - () => BasicAuthorizationHeader( - username: v.$1, - password: v.$2, - ), - matcher); + 'then it ${matcher.describe(StringDescription())}', + () => BasicAuthorizationHeader(username: v.$1, password: v.$2), + matcher, + ); }, ); @@ -581,92 +534,81 @@ void main() { }, ); - test( - 'when BearerAuthorizationHeader.toStringInsecure() is called ' - 'then it should expose the full token', - () { - final bearer = BearerAuthorizationHeader(token: 'secretToken123'); - final result = bearer.toStringInsecure(); - expect(result, 'BearerAuthorizationHeader(token: secretToken123)'); - }, - ); + test('when BearerAuthorizationHeader.toStringInsecure() is called ' + 'then it should expose the full token', () { + final bearer = BearerAuthorizationHeader(token: 'secretToken123'); + final result = bearer.toStringInsecure(); + expect(result, 'BearerAuthorizationHeader(token: secretToken123)'); + }); - test( - 'when BasicAuthorizationHeader.toString() is called ' - 'then it should mask the password', - () { - final basic = BasicAuthorizationHeader( - username: 'user', - password: 'secretPassword', - ); - final result = basic.toString(); - expect( - result, 'BasicAuthorizationHeader(username: user, password: ****)'); - expect(result, isNot(contains('secretPassword'))); - }, - ); + test('when BasicAuthorizationHeader.toString() is called ' + 'then it should mask the password', () { + final basic = BasicAuthorizationHeader( + username: 'user', + password: 'secretPassword', + ); + final result = basic.toString(); + expect( + result, + 'BasicAuthorizationHeader(username: user, password: ****)', + ); + expect(result, isNot(contains('secretPassword'))); + }); - test( - 'when BasicAuthorizationHeader.toStringInsecure() is called ' - 'then it should expose the full password', - () { - final basic = BasicAuthorizationHeader( - username: 'user', - password: 'secretPassword', - ); - final result = basic.toStringInsecure(); - expect(result, - 'BasicAuthorizationHeader(username: user, password: secretPassword)'); - }, - ); + test('when BasicAuthorizationHeader.toStringInsecure() is called ' + 'then it should expose the full password', () { + final basic = BasicAuthorizationHeader( + username: 'user', + password: 'secretPassword', + ); + final result = basic.toStringInsecure(); + expect( + result, + 'BasicAuthorizationHeader(username: user, password: secretPassword)', + ); + }); - test( - 'when DigestAuthorizationHeader.toString() is called ' - 'then it should mask sensitive fields', - () { - final digest = DigestAuthorizationHeader( - username: 'user', - realm: 'realm', - nonce: 'secretNonce', - uri: '/path', - response: 'secretResponse', - cnonce: 'secretCnonce', - opaque: 'secretOpaque', - ); - final result = digest.toString(); - expect(result, contains('username: user')); - expect(result, contains('realm: realm')); - expect(result, contains('uri: /path')); - expect(result, contains('nonce: ****')); - expect(result, contains('response: ****')); - expect(result, contains('cnonce: ****')); - expect(result, contains('opaque: ****')); - expect(result, isNot(contains('secretNonce'))); - expect(result, isNot(contains('secretResponse'))); - expect(result, isNot(contains('secretCnonce'))); - expect(result, isNot(contains('secretOpaque'))); - }, - ); + test('when DigestAuthorizationHeader.toString() is called ' + 'then it should mask sensitive fields', () { + final digest = DigestAuthorizationHeader( + username: 'user', + realm: 'realm', + nonce: 'secretNonce', + uri: '/path', + response: 'secretResponse', + cnonce: 'secretCnonce', + opaque: 'secretOpaque', + ); + final result = digest.toString(); + expect(result, contains('username: user')); + expect(result, contains('realm: realm')); + expect(result, contains('uri: /path')); + expect(result, contains('nonce: ****')); + expect(result, contains('response: ****')); + expect(result, contains('cnonce: ****')); + expect(result, contains('opaque: ****')); + expect(result, isNot(contains('secretNonce'))); + expect(result, isNot(contains('secretResponse'))); + expect(result, isNot(contains('secretCnonce'))); + expect(result, isNot(contains('secretOpaque'))); + }); - test( - 'when DigestAuthorizationHeader.toStringInsecure() is called ' - 'then it should expose all sensitive fields', - () { - final digest = DigestAuthorizationHeader( - username: 'user', - realm: 'realm', - nonce: 'secretNonce', - uri: '/path', - response: 'secretResponse', - cnonce: 'secretCnonce', - opaque: 'secretOpaque', - ); - final result = digest.toStringInsecure(); - expect(result, contains('nonce: secretNonce')); - expect(result, contains('response: secretResponse')); - expect(result, contains('cnonce: secretCnonce')); - expect(result, contains('opaque: secretOpaque')); - }, - ); + test('when DigestAuthorizationHeader.toStringInsecure() is called ' + 'then it should expose all sensitive fields', () { + final digest = DigestAuthorizationHeader( + username: 'user', + realm: 'realm', + nonce: 'secretNonce', + uri: '/path', + response: 'secretResponse', + cnonce: 'secretCnonce', + opaque: 'secretOpaque', + ); + final result = digest.toStringInsecure(); + expect(result, contains('nonce: secretNonce')); + expect(result, contains('response: secretResponse')); + expect(result, contains('cnonce: secretCnonce')); + expect(result, contains('opaque: secretOpaque')); + }); }); } diff --git a/test/headers/typed/cache_control_header_test.dart b/test/headers/typed/cache_control_header_test.dart index f2f48d57..6584d7ef 100644 --- a/test/headers/typed/cache_control_header_test.dart +++ b/test/headers/typed/cache_control_header_test.dart @@ -110,7 +110,7 @@ void main() { server: server, touchHeaders: (final h) => h.cacheControl, headers: { - 'cache-control': 'max-age=3600, stale-while-revalidate=300' + 'cache-control': 'max-age=3600, stale-while-revalidate=300', }, ), throwsA( @@ -126,20 +126,17 @@ void main() { }, ); - test( - 'when a Cache-Control header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {}, - ); + test('when a Cache-Control header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid Cache-Control header is passed then it should parse the directives correctly', @@ -336,19 +333,16 @@ void main() { tearDown(() => server.close()); group('when an invalid Cache-Control header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'cache-control': 'invalid-directive'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'cache-control': 'invalid-directive'}, + ); - expect(Headers.cacheControl[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.cacheControl, throwsInvalidHeader); - }, - ); + expect(Headers.cacheControl[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.cacheControl, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/clear_site_data_header_test.dart b/test/headers/typed/clear_site_data_header_test.dart index fa6a52c3..7f7c7425 100644 --- a/test/headers/typed/clear_site_data_header_test.dart +++ b/test/headers/typed/clear_site_data_header_test.dart @@ -27,11 +27,13 @@ void main() { touchHeaders: (final h) => h.clearSiteData, headers: {'clear-site-data': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -47,11 +49,13 @@ void main() { touchHeaders: (final h) => h.clearSiteData, headers: {'clear-site-data': 'invalidValue'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid value'), + ), + ), ); }, ); @@ -67,28 +71,27 @@ void main() { touchHeaders: (final h) => h.clearSiteData, headers: {'clear-site-data': '"cache", "*", "cookies"'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Wildcard (*) cannot be used with other values'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Wildcard (*) cannot be used with other values'), + ), + ), ); }, ); - test( - 'when a Clear-Site-Data header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'clear-site-data': '"cache", "*", "cookies"'}, - ); - expect(headers, isNotNull); - }, - ); + test('when a Clear-Site-Data header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'clear-site-data': '"cache", "*", "cookies"'}, + ); + expect(headers, isNotNull); + }); test( 'when a valid Clear-Site-Data header is passed then it should parse the data types correctly', @@ -145,19 +148,16 @@ void main() { tearDown(() => server.close()); group('When an empty Clear-Site-Data header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'clear-site-data': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'clear-site-data': ''}, + ); - expect(Headers.clearSiteData[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.clearSiteData, throwsInvalidHeader); - }, - ); + expect(Headers.clearSiteData[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.clearSiteData, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/connection_header_test.dart b/test/headers/typed/connection_header_test.dart index c0b29e8e..7854b8ce 100644 --- a/test/headers/typed/connection_header_test.dart +++ b/test/headers/typed/connection_header_test.dart @@ -16,64 +16,55 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty Connection header is passed then the server responds ' - 'with a bad request including a message that states the directives ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.connection, - headers: {'connection': ''}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), + test('when an empty Connection header is passed then the server responds ' + 'with a bad request including a message that states the directives ' + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.connection, + headers: {'connection': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); + ), + ); + }); - test( - 'when an invalid Connection header is passed then the server responds ' - 'with a bad request including a message that states the value ' - 'is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.connection, - headers: {'connection': 'custom-directive'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - ), + test('when an invalid Connection header is passed then the server responds ' + 'with a bad request including a message that states the value ' + 'is invalid', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.connection, + headers: {'connection': 'custom-directive'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid value'), ), - ); - }, - ); + ), + ); + }); - test( - 'when a Connection header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'connection': 'invalid-connection-format'}, - ); + test('when a Connection header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'connection': 'invalid-connection-format'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a Connection header with directives are passed then they should be parsed correctly', @@ -91,22 +82,19 @@ void main() { }, ); - test( - 'when a Connection header with duplicate directives are passed then ' - 'they should be parsed correctly and remove duplicates', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.connection, - headers: {'connection': 'keep-alive, upgrade, keep-alive'}, - ); - - expect( - headers.connection?.directives.map((final d) => d.value), - containsAll(['keep-alive', 'upgrade']), - ); - }, - ); + test('when a Connection header with duplicate directives are passed then ' + 'they should be parsed correctly and remove duplicates', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.connection, + headers: {'connection': 'keep-alive, upgrade, keep-alive'}, + ); + + expect( + headers.connection?.directives.map((final d) => d.value), + containsAll(['keep-alive', 'upgrade']), + ); + }); test( 'when a Connection header with keep-alive is passed then isKeepAlive should be true', @@ -144,23 +132,17 @@ void main() { tearDown(() => server.close()); - group( - 'when an invalid Connection header is passed', - () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'connection': ''}, - ); - - expect(Headers.connection[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.connection, throwsInvalidHeader); - }, + group('when an invalid Connection header is passed', () { + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'connection': ''}, ); - }, - ); + + expect(Headers.connection[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.connection, throwsInvalidHeader); + }); + }); }); } diff --git a/test/headers/typed/content_disposition_header_test.dart b/test/headers/typed/content_disposition_header_test.dart index 47ee8b96..4cec1edc 100644 --- a/test/headers/typed/content_disposition_header_test.dart +++ b/test/headers/typed/content_disposition_header_test.dart @@ -26,38 +26,37 @@ void main() { touchHeaders: (final h) => h.contentDisposition, headers: {'content-disposition': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); - test( - 'when a Content-Disposition header with an empty value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-disposition': ''}, - ); + test('when a Content-Disposition header with an empty value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-disposition': ''}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a Content-Disposition header is passed then it should parse correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { - 'content-disposition': 'attachment; filename="example.txt"' + 'content-disposition': 'attachment; filename="example.txt"', }, ); @@ -92,10 +91,10 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'content-disposition': - 'attachment; filename="example.txt"; size=12345' + 'attachment; filename="example.txt"; size=12345', }, ); @@ -143,20 +142,19 @@ void main() { tearDown(() => server.close()); group('when an empty Content-Disposition header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-disposition': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-disposition': ''}, + ); - expect( - Headers.contentDisposition[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.contentDisposition, throwsInvalidHeader); - }, - ); + expect( + Headers.contentDisposition[headers].valueOrNullIfInvalid, + isNull, + ); + expect(() => headers.contentDisposition, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/content_encoding_header_test.dart b/test/headers/typed/content_encoding_header_test.dart index 54fd37ef..bb3944e8 100644 --- a/test/headers/typed/content_encoding_header_test.dart +++ b/test/headers/typed/content_encoding_header_test.dart @@ -60,20 +60,17 @@ void main() { }, ); - test( - 'when a Content-Encoding header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-encoding': 'custom-encoding'}, - ); + test('when a Content-Encoding header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-encoding': 'custom-encoding'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a single valid encoding is passed then it should parse correctly', @@ -105,41 +102,31 @@ void main() { ); group('when multiple Content-Encoding encodings are passed', () { - test( - 'then they should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.contentEncoding, - headers: {'content-encoding': 'gzip, deflate'}, - ); + test('then they should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.contentEncoding, + headers: {'content-encoding': 'gzip, deflate'}, + ); - expect( - headers.contentEncoding?.encodings - .map((final e) => e.name) - .toList(), - equals(['gzip', 'deflate']), - ); - }, - ); + expect( + headers.contentEncoding?.encodings.map((final e) => e.name).toList(), + equals(['gzip', 'deflate']), + ); + }); - test( - 'with extra whitespace should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.contentEncoding, - headers: {'content-encoding': ' gzip , deflate '}, - ); + test('with extra whitespace should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.contentEncoding, + headers: {'content-encoding': ' gzip , deflate '}, + ); - expect( - headers.contentEncoding?.encodings - .map((final e) => e.name) - .toList(), - equals(['gzip', 'deflate']), - ); - }, - ); + expect( + headers.contentEncoding?.encodings.map((final e) => e.name).toList(), + equals(['gzip', 'deflate']), + ); + }); test( 'with duplicate encodings should parse correctly and remove duplicates', @@ -171,19 +158,16 @@ void main() { tearDown(() => server.close()); group('when an invalid Content-Encoding header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-encoding': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-encoding': ''}, + ); - expect(Headers.contentEncoding[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.contentEncoding, throwsInvalidHeader); - }, - ); + expect(Headers.contentEncoding[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.contentEncoding, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/content_language_header_test.dart b/test/headers/typed/content_language_header_test.dart index 293c9d82..1326f10f 100644 --- a/test/headers/typed/content_language_header_test.dart +++ b/test/headers/typed/content_language_header_test.dart @@ -59,20 +59,17 @@ void main() { }, ); - test( - 'when a Content-Language header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-language': 'en-123'}, - ); + test('when a Content-Language header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-language': 'en-123'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a single valid language is passed then it should parse correctly', @@ -109,16 +106,20 @@ void main() { }); test( - 'with duplicate languages then they should parse correctly and remove duplicates', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.contentLanguage, - headers: {'content-language': 'en, fr, de, en'}, - ); + 'with duplicate languages then they should parse correctly and remove duplicates', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.contentLanguage, + headers: {'content-language': 'en, fr, de, en'}, + ); - expect(headers.contentLanguage?.languages, equals(['en', 'fr', 'de'])); - }); + expect( + headers.contentLanguage?.languages, + equals(['en', 'fr', 'de']), + ); + }, + ); }); test( @@ -145,19 +146,16 @@ void main() { tearDown(() => server.close()); group('when an invalid Content-Language header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-language': 'en-123'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-language': 'en-123'}, + ); - expect(Headers.contentLanguage[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.contentLanguage, throwsInvalidHeader); - }, - ); + expect(Headers.contentLanguage[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.contentLanguage, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/content_range_header_test.dart b/test/headers/typed/content_range_header_test.dart index 8bc58ba4..df141618 100644 --- a/test/headers/typed/content_range_header_test.dart +++ b/test/headers/typed/content_range_header_test.dart @@ -82,42 +82,36 @@ void main() { }, ); - test( - 'when an invalid Content-Range header with start greater than end is ' - 'passed then the server responds with a bad request including a message ' - 'that states the header value has an invalid range', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.contentRange, - headers: {'content-range': 'bytes 500-499/1234'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid range'), - ), - ), - ); - }, - ); - - test( - 'when a Content-Range header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + test('when an invalid Content-Range header with start greater than end is ' + 'passed then the server responds with a bad request including a message ' + 'that states the header value has an invalid range', () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (final h) => h.contentRange, headers: {'content-range': 'bytes 500-499/1234'}, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid range'), + ), + ), + ); + }); - expect(headers, isNotNull); - }, - ); + test('when a Content-Range header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-range': 'bytes 500-499/1234'}, + ); + + expect(headers, isNotNull); + }); test( 'when a Content-Range header with a valid byte range is passed then it ' @@ -194,19 +188,16 @@ void main() { tearDown(() => server.close()); group('when an invalid Content-Range header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-range': 'bytes 0-499/invalid'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-range': 'bytes 0-499/invalid'}, + ); - expect(Headers.contentRange[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.contentRange, throwsInvalidHeader); - }, - ); + expect(Headers.contentRange[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.contentRange, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/content_security_policy_header_test.dart b/test/headers/typed/content_security_policy_header_test.dart index a71c337c..8f17795f 100644 --- a/test/headers/typed/content_security_policy_header_test.dart +++ b/test/headers/typed/content_security_policy_header_test.dart @@ -26,38 +26,37 @@ void main() { touchHeaders: (final h) => h.contentSecurityPolicy, headers: {'content-security-policy': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); - test( - 'when a Content-Security-Policy header with an empty value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-security-policy': ''}, - ); + test('when a Content-Security-Policy header with an empty value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-security-policy': ''}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid Content-Security-Policy header is passed then it should parse the directives correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { - 'content-security-policy': "default-src 'self'; script-src 'none'" + 'content-security-policy': "default-src 'self'; script-src 'none'", }, ); @@ -75,10 +74,10 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'content-security-policy': - "default-src 'self'; img-src *; media-src media1.com media2.com" + "default-src 'self'; img-src *; media-src media1.com media2.com", }, ); @@ -117,20 +116,19 @@ void main() { tearDown(() => server.close()); group('when an empty Content-Security-Policy header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'content-security-policy': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'content-security-policy': ''}, + ); - expect(Headers.contentSecurityPolicy[headers].valueOrNullIfInvalid, - isNull); - expect(() => headers.contentSecurityPolicy, throwsInvalidHeader); - }, - ); + expect( + Headers.contentSecurityPolicy[headers].valueOrNullIfInvalid, + isNull, + ); + expect(() => headers.contentSecurityPolicy, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/cookie_header_test.dart b/test/headers/typed/cookie_header_test.dart index f16cd06f..4a86e7b5 100644 --- a/test/headers/typed/cookie_header_test.dart +++ b/test/headers/typed/cookie_header_test.dart @@ -98,20 +98,17 @@ void main() { }, ); - test( - 'when a Cookie header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'cookie': 'sessionId=abc123; userId=42\x7F'}, - ); + test('when a Cookie header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'cookie': 'sessionId=abc123; userId=42\x7F'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid Cookie header is passed with an empty name then it should parse the cookies correctly', @@ -230,7 +227,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'cookie': 'sessionId=abc123; invalidCookie'}, ); diff --git a/test/headers/typed/cross_origin_embedder_policy_header_test.dart b/test/headers/typed/cross_origin_embedder_policy_header_test.dart index 9072ae56..e9d63993 100644 --- a/test/headers/typed/cross_origin_embedder_policy_header_test.dart +++ b/test/headers/typed/cross_origin_embedder_policy_header_test.dart @@ -7,124 +7,119 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Cross-Origin-Embedder-Policy header with validation', - () { - late RelicServer server; + group('Given a Cross-Origin-Embedder-Policy header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when an empty Cross-Origin-Embedder-Policy header is passed then the server should respond with a bad request ' - 'including a message that states the value cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.crossOriginEmbedderPolicy, - headers: {'cross-origin-embedder-policy': ''}, - ), - throwsA(isA().having( + test( + 'when an empty Cross-Origin-Embedder-Policy header is passed then the server should respond with a bad request ' + 'including a message that states the value cannot be empty', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.crossOriginEmbedderPolicy, + headers: {'cross-origin-embedder-policy': ''}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Value cannot be empty'), - )), - ); - }, - ); - - test( - 'when an invalid Cross-Origin-Embedder-Policy header is passed then the server should respond with a bad request ' - 'including a message that states the value is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.crossOriginEmbedderPolicy, - headers: {'cross-origin-embedder-policy': 'custom-policy'}, ), - throwsA(isA().having( + ), + ); + }, + ); + + test( + 'when an invalid Cross-Origin-Embedder-Policy header is passed then the server should respond with a bad request ' + 'including a message that states the value is invalid', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.crossOriginEmbedderPolicy, + headers: {'cross-origin-embedder-policy': 'custom-policy'}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Invalid value'), - )), - ); - }, - ); + ), + ), + ); + }, + ); - test( - 'when a Cross-Origin-Embedder-Policy header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'cross-origin-embedder-policy': 'custom-policy'}, - ); - expect(headers, isNotNull); - }, - ); + test( + 'when a Cross-Origin-Embedder-Policy header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'cross-origin-embedder-policy': 'custom-policy'}, + ); + expect(headers, isNotNull); + }, + ); - test( - 'when a valid Cross-Origin-Embedder-Policy header is passed then it should parse the policy correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.crossOriginEmbedderPolicy, - headers: {'cross-origin-embedder-policy': 'require-corp'}, - ); + test( + 'when a valid Cross-Origin-Embedder-Policy header is passed then it should parse the policy correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.crossOriginEmbedderPolicy, + headers: {'cross-origin-embedder-policy': 'require-corp'}, + ); - expect( - headers.crossOriginEmbedderPolicy?.policy, - equals('require-corp'), - ); - }, - ); + expect( + headers.crossOriginEmbedderPolicy?.policy, + equals('require-corp'), + ); + }, + ); - test( - 'when no Cross-Origin-Embedder-Policy header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.crossOriginEmbedderPolicy, - headers: {}, - ); + test( + 'when no Cross-Origin-Embedder-Policy header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.crossOriginEmbedderPolicy, + headers: {}, + ); - expect(headers.crossOriginEmbedderPolicy, isNull); - }, - ); - }, - ); + expect(headers.crossOriginEmbedderPolicy, isNull); + }, + ); + }); - group( - 'Given a Cross-Origin-Embedder-Policy header without validation', - () { - late RelicServer server; + group('Given a Cross-Origin-Embedder-Policy header without validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - group('When an empty Cross-Origin-Embedder-Policy header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {}, - ); - expect(headers.crossOriginEmbedderPolicy, isNull); - }, + group('When an empty Cross-Origin-Embedder-Policy header is passed', () { + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {}, ); + expect(headers.crossOriginEmbedderPolicy, isNull); }); - }, - ); + }); + }); } diff --git a/test/headers/typed/cross_origin_opener_policy_header_test.dart b/test/headers/typed/cross_origin_opener_policy_header_test.dart index 5101959f..c5c1757f 100644 --- a/test/headers/typed/cross_origin_opener_policy_header_test.dart +++ b/test/headers/typed/cross_origin_opener_policy_header_test.dart @@ -26,11 +26,13 @@ void main() { touchHeaders: (final h) => h.crossOriginOpenerPolicy, headers: {'cross-origin-opener-policy': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -45,11 +47,13 @@ void main() { touchHeaders: (final h) => h.crossOriginOpenerPolicy, headers: {'cross-origin-opener-policy': 'custom-policy'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid value'), + ), + ), ); }, ); @@ -61,7 +65,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'cross-origin-opener-policy': 'custom-policy'}, ); expect(headers, isNotNull); @@ -105,17 +109,14 @@ void main() { tearDown(() => server.close()); group('When an empty Cross-Origin-Opener-Policy header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {}, - ); - expect(headers.crossOriginOpenerPolicy, isNull); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {}, + ); + expect(headers.crossOriginOpenerPolicy, isNull); + }); }); }); } diff --git a/test/headers/typed/cross_origin_resource_policy_header_test.dart b/test/headers/typed/cross_origin_resource_policy_header_test.dart index e7f753a4..30276052 100644 --- a/test/headers/typed/cross_origin_resource_policy_header_test.dart +++ b/test/headers/typed/cross_origin_resource_policy_header_test.dart @@ -7,127 +7,119 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Cross-Origin-Resource-Policy header with validation', - () { - late RelicServer server; + group('Given a Cross-Origin-Resource-Policy header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - test( - 'when an empty Cross-Origin-Resource-Policy header is passed then the server should respond with a bad request ' - 'including a message that states the value cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.crossOriginResourcePolicy, - headers: {'cross-origin-resource-policy': ''}, - ), - throwsA(isA().having( + test( + 'when an empty Cross-Origin-Resource-Policy header is passed then the server should respond with a bad request ' + 'including a message that states the value cannot be empty', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.crossOriginResourcePolicy, + headers: {'cross-origin-resource-policy': ''}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Value cannot be empty'), - )), - ); - }, - ); - - test( - 'when an invalid Cross-Origin-Resource-Policy header is passed then the server should respond with a bad request ' - 'including a message that states the value is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.crossOriginResourcePolicy, - headers: {'cross-origin-resource-policy': 'custom-policy'}, ), - throwsA(isA().having( + ), + ); + }, + ); + + test( + 'when an invalid Cross-Origin-Resource-Policy header is passed then the server should respond with a bad request ' + 'including a message that states the value is invalid', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.crossOriginResourcePolicy, + headers: {'cross-origin-resource-policy': 'custom-policy'}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Invalid value'), - )), - ); - }, - ); + ), + ), + ); + }, + ); - test( - 'when a Cross-Origin-Resource-Policy header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'cross-origin-resource-policy': 'custom-policy'}, - ); - expect(headers, isNotNull); - }, - ); + test( + 'when a Cross-Origin-Resource-Policy header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'cross-origin-resource-policy': 'custom-policy'}, + ); + expect(headers, isNotNull); + }, + ); - test( - 'when a valid Cross-Origin-Resource-Policy header is passed then it should parse the policy correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.crossOriginResourcePolicy, - headers: {'cross-origin-resource-policy': 'same-origin'}, - ); + test( + 'when a valid Cross-Origin-Resource-Policy header is passed then it should parse the policy correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.crossOriginResourcePolicy, + headers: {'cross-origin-resource-policy': 'same-origin'}, + ); - expect( - headers.crossOriginResourcePolicy?.policy, - equals('same-origin'), - ); - }, - ); + expect( + headers.crossOriginResourcePolicy?.policy, + equals('same-origin'), + ); + }, + ); - test( - 'when no Cross-Origin-Resource-Policy header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.crossOriginResourcePolicy, - headers: {}, - ); + test( + 'when no Cross-Origin-Resource-Policy header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.crossOriginResourcePolicy, + headers: {}, + ); - expect(headers.crossOriginResourcePolicy, isNull); - }, - ); - }, - ); + expect(headers.crossOriginResourcePolicy, isNull); + }, + ); + }); - group( - 'Given a Cross-Origin-Resource-Policy header without validation', - () { - late RelicServer server; + group('Given a Cross-Origin-Resource-Policy header without validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); + tearDown(() => server.close()); - group( - 'When an empty Cross-Origin-Resource-Policy header is passed', - () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {}, - ); - expect(headers.crossOriginResourcePolicy, isNull); - }, - ); - }, - ); - }, - ); + group('When an empty Cross-Origin-Resource-Policy header is passed', () { + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {}, + ); + expect(headers.crossOriginResourcePolicy, isNull); + }); + }); + }); } diff --git a/test/headers/typed/etag_header_test.dart b/test/headers/typed/etag_header_test.dart index ceb8a5b7..3469c57d 100644 --- a/test/headers/typed/etag_header_test.dart +++ b/test/headers/typed/etag_header_test.dart @@ -16,27 +16,24 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty ETag header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.etag, - headers: {'etag': ''}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), + test('when an empty ETag header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.etag, + headers: {'etag': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); + ), + ); + }); test( 'when an invalid ETag is passed then the server responds with a bad request ' @@ -59,20 +56,17 @@ void main() { }, ); - test( - 'when an ETag header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'etag': '123456'}, - ); + test('when an ETag header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'etag': '123456'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid strong ETag is passed then it should parse correctly', @@ -102,18 +96,15 @@ void main() { }, ); - test( - 'when no ETag header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.etag, - headers: {}, - ); + test('when no ETag header is passed then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.etag, + headers: {}, + ); - expect(headers.etag, isNull); - }, - ); + expect(headers.etag, isNull); + }); }); group('Given an ETag header without validation', () { @@ -126,19 +117,16 @@ void main() { tearDown(() => server.close()); group('when an invalid ETag header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'etag': '123456'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'etag': '123456'}, + ); - expect(Headers.etag[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.etag, throwsInvalidHeader); - }, - ); + expect(Headers.etag[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.etag, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/expect_header_test.dart b/test/headers/typed/expect_header_test.dart index 3f600038..e7a5bf4b 100644 --- a/test/headers/typed/expect_header_test.dart +++ b/test/headers/typed/expect_header_test.dart @@ -16,61 +16,57 @@ void main() { tearDown(() => server.close()); + test('when an empty Expect header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.expect, + headers: {'expect': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), + ); + }); + test( - 'when an empty Expect header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', + 'when an invalid Expect header is passed then the server should respond with a bad request ' + 'including a message that states the value is invalid', () async { expect( getServerRequestHeaders( server: server, touchHeaders: (final h) => h.expect, - headers: {'expect': ''}, + headers: {'expect': 'custom-directive'}, ), throwsA( isA().having( (final e) => e.message, 'message', - contains('Value cannot be empty'), + contains('Invalid value'), ), ), ); }, ); - test( - 'when an invalid Expect header is passed then the server should respond with a bad request ' - 'including a message that states the value is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.expect, - headers: {'expect': 'custom-directive'}, - ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - )), - ); - }, - ); - - test( - 'when an Expect header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'expect': 'custom-directive'}, - ); + test('when an Expect header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'expect': 'custom-directive'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid Expect header is passed then it should parse the directives correctly', @@ -81,10 +77,7 @@ void main() { headers: {'expect': '100-continue'}, ); - expect( - headers.expect?.value, - contains('100-continue'), - ); + expect(headers.expect?.value, contains('100-continue')); }, ); }); @@ -99,19 +92,16 @@ void main() { tearDown(() => server.close()); group('when an empty Expect header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'expect': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'expect': ''}, + ); - expect(Headers.expect[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.expect, throwsInvalidHeader); - }, - ); + expect(Headers.expect[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.expect, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/forwarded_header_behavior_test.dart b/test/headers/typed/forwarded_header_behavior_test.dart index c73c48b2..fa8714cf 100644 --- a/test/headers/typed/forwarded_header_behavior_test.dart +++ b/test/headers/typed/forwarded_header_behavior_test.dart @@ -2,15 +2,16 @@ import 'package:relic/relic.dart'; import 'package:test/test.dart'; void main() { - final element1 = - ForwardedElement(forwardedFor: const ForwardedIdentifier('client1')); + final element1 = ForwardedElement( + forwardedFor: const ForwardedIdentifier('client1'), + ); final element2 = ForwardedElement(by: const ForwardedIdentifier('proxy1')); - final element3 = - ForwardedElement(forwardedFor: const ForwardedIdentifier('client2')); + final element3 = ForwardedElement( + forwardedFor: const ForwardedIdentifier('client2'), + ); group('Equality and HashCode', () { - test( - 'Given two identical ForwardedHeader instances, ' + test('Given two identical ForwardedHeader instances, ' 'when compared with ==, ' 'then they should be equal and have the same hashCode.', () { final header1 = ForwardedHeader([element1, element2]); @@ -20,8 +21,7 @@ void main() { expect(header1.hashCode, equals(header2.hashCode)); }); - test( - 'Given two ForwardedHeader instances with different element lists, ' + test('Given two ForwardedHeader instances with different element lists, ' 'when compared with ==, ' 'then they should not be equal.', () { final header1 = ForwardedHeader([element1, element2]); @@ -31,13 +31,15 @@ void main() { }); test( - 'Given two ForwardedHeader instances with elements in different order, ' - 'when compared with ==, ' - 'then they should not be equal (order matters for ListEquality).', () { - final header1 = ForwardedHeader([element1, element2]); - final header2 = ForwardedHeader([element2, element1]); + 'Given two ForwardedHeader instances with elements in different order, ' + 'when compared with ==, ' + 'then they should not be equal (order matters for ListEquality).', + () { + final header1 = ForwardedHeader([element1, element2]); + final header2 = ForwardedHeader([element2, element1]); - expect(header1 == header2, isFalse); - }); + expect(header1 == header2, isFalse); + }, + ); }); } diff --git a/test/headers/typed/forwarded_header_test.dart b/test/headers/typed/forwarded_header_test.dart index 86199119..bc91c31b 100644 --- a/test/headers/typed/forwarded_header_test.dart +++ b/test/headers/typed/forwarded_header_test.dart @@ -5,41 +5,45 @@ void main() { group('ForwardedHeader Parsing Logic', () { group('Given single forwarded-element strings,', () { test( - 'when parsing "for=_gazonk", ' - 'then the ForwardedHeader contains one element with a ForwardedNode for "for".', - () { - const headerValue = 'for="_gazonk"'; - const expectedNode = ForwardedIdentifier('_gazonk'); + 'when parsing "for=_gazonk", ' + 'then the ForwardedHeader contains one element with a ForwardedNode for "for".', + () { + const headerValue = 'for="_gazonk"'; + const expectedNode = ForwardedIdentifier('_gazonk'); - final parsedHeader = ForwardedHeader.parse([headerValue]); + final parsedHeader = ForwardedHeader.parse([headerValue]); - expect(parsedHeader, isNotNull); - expect(parsedHeader.elements, hasLength(1)); - final element = parsedHeader.elements.first; - expect(element.forwardedFor, equals(expectedNode)); - expect(element.by, isNull); - expect(element.host, isNull); - expect(element.proto, isNull); - expect(element.extensions, isNull); - }); + expect(parsedHeader, isNotNull); + expect(parsedHeader.elements, hasLength(1)); + final element = parsedHeader.elements.first; + expect(element.forwardedFor, equals(expectedNode)); + expect(element.by, isNull); + expect(element.host, isNull); + expect(element.proto, isNull); + expect(element.extensions, isNull); + }, + ); test( - 'when parsing \'For="[2001:db8:cafe::17]:4711"\', ' - 'then the ForwardedHeader contains one element with an IPv6 ForwardedNode for "for".', - () { - const headerValue = 'For="[2001:db8:cafe::17]:4711"'; - const expectedNode = ForwardedIdentifier('[2001:db8:cafe::17]', '4711'); + 'when parsing \'For="[2001:db8:cafe::17]:4711"\', ' + 'then the ForwardedHeader contains one element with an IPv6 ForwardedNode for "for".', + () { + const headerValue = 'For="[2001:db8:cafe::17]:4711"'; + const expectedNode = ForwardedIdentifier( + '[2001:db8:cafe::17]', + '4711', + ); - final parsedHeader = ForwardedHeader.parse([headerValue]); + final parsedHeader = ForwardedHeader.parse([headerValue]); - expect(parsedHeader, isNotNull); - expect(parsedHeader.elements, hasLength(1)); - final element = parsedHeader.elements.first; - expect(element.forwardedFor, equals(expectedNode)); - }); + expect(parsedHeader, isNotNull); + expect(parsedHeader.elements, hasLength(1)); + final element = parsedHeader.elements.first; + expect(element.forwardedFor, equals(expectedNode)); + }, + ); - test( - 'when parsing "for=192.0.2.60;proto=http;by=203.0.113.43", ' + test('when parsing "for=192.0.2.60;proto=http;by=203.0.113.43", ' 'then all parameters are correctly parsed.', () { const headerValue = 'for=192.0.2.60;proto=http;by=203.0.113.43'; const expectedForNode = ForwardedIdentifier('192.0.2.60'); @@ -56,8 +60,7 @@ void main() { expect(element.host, isNull); }); - test( - 'when parsing "by=example.com;host=myhost.local;ext=foo", ' + test('when parsing "by=example.com;host=myhost.local;ext=foo", ' 'then by, host, and extension parameters are correctly parsed.', () { const headerValue = 'by=example.com;host=myhost.local;ext=foo'; const expectedByNode = ForwardedIdentifier('example.com'); @@ -75,8 +78,7 @@ void main() { expect(element.proto, isNull); }); - test( - 'when parsing "for=unknown", ' + test('when parsing "for=unknown", ' 'then the "for" node is "unknown".', () { const headerValue = 'for=unknown'; const expectedNode = ForwardedIdentifier('unknown'); @@ -88,24 +90,25 @@ void main() { }); test( - 'when parsing with mixed case parameter names like "FoR=1.2.3.4;PrOtO=https", ' - 'then parameters are parsed case-insensitively.', () { - const headerValue = 'FoR=1.2.3.4;PrOtO=https'; - const expectedForNode = ForwardedIdentifier('1.2.3.4'); + 'when parsing with mixed case parameter names like "FoR=1.2.3.4;PrOtO=https", ' + 'then parameters are parsed case-insensitively.', + () { + const headerValue = 'FoR=1.2.3.4;PrOtO=https'; + const expectedForNode = ForwardedIdentifier('1.2.3.4'); - final parsedHeader = ForwardedHeader.parse([headerValue]); + final parsedHeader = ForwardedHeader.parse([headerValue]); - expect(parsedHeader, isNotNull); - expect(parsedHeader.elements, hasLength(1)); - final element = parsedHeader.elements.first; - expect(element.forwardedFor, equals(expectedForNode)); - expect(element.proto, equals('https')); - }); + expect(parsedHeader, isNotNull); + expect(parsedHeader.elements, hasLength(1)); + final element = parsedHeader.elements.first; + expect(element.forwardedFor, equals(expectedForNode)); + expect(element.proto, equals('https')); + }, + ); }); group('Given multiple forwarded-element strings,', () { - test( - 'when parsing "for=192.0.2.43, for=198.51.100.17", ' + test('when parsing "for=192.0.2.43, for=198.51.100.17", ' 'then two elements are parsed correctly.', () { const headerValue = 'for=192.0.2.43, for=198.51.100.17'; const expectedNode1 = ForwardedIdentifier('192.0.2.43'); @@ -120,170 +123,160 @@ void main() { }); test( - 'when parsing from multiple header lines ["for=client1", "for=proxy1"], ' - 'then they are combined and parsed as two elements.', () { - // Simulates multiple header fields - final headerValues = ['for=client1', 'for=proxy1']; - const expectedNode1 = ForwardedIdentifier('client1'); - const expectedNode2 = ForwardedIdentifier('proxy1'); + 'when parsing from multiple header lines ["for=client1", "for=proxy1"], ' + 'then they are combined and parsed as two elements.', + () { + // Simulates multiple header fields + final headerValues = ['for=client1', 'for=proxy1']; + const expectedNode1 = ForwardedIdentifier('client1'); + const expectedNode2 = ForwardedIdentifier('proxy1'); - // ForwardedHeader.parse internally joins with ", " which simulates HTTP combining them - final parsedHeader = ForwardedHeader.parse(headerValues); + // ForwardedHeader.parse internally joins with ", " which simulates HTTP combining them + final parsedHeader = ForwardedHeader.parse(headerValues); - expect(parsedHeader, isNotNull); - expect(parsedHeader.elements, hasLength(2)); - expect(parsedHeader.elements[0].forwardedFor, equals(expectedNode1)); - expect(parsedHeader.elements[1].forwardedFor, equals(expectedNode2)); - }); + expect(parsedHeader, isNotNull); + expect(parsedHeader.elements, hasLength(2)); + expect(parsedHeader.elements[0].forwardedFor, equals(expectedNode1)); + expect(parsedHeader.elements[1].forwardedFor, equals(expectedNode2)); + }, + ); test( - 'when parsing complex mixed elements "for=a;host=x, by=b;proto=y, for=c;ext=z", ' - 'then all elements and their parameters are parsed.', () { - const headerValue = 'for=a;host=x, by=b;proto=y, for=c;ext=z'; + 'when parsing complex mixed elements "for=a;host=x, by=b;proto=y, for=c;ext=z", ' + 'then all elements and their parameters are parsed.', + () { + const headerValue = 'for=a;host=x, by=b;proto=y, for=c;ext=z'; - final parsedHeader = ForwardedHeader.parse([headerValue]); + final parsedHeader = ForwardedHeader.parse([headerValue]); - expect(parsedHeader, isNotNull); - expect(parsedHeader.elements, hasLength(3)); - - final el1 = parsedHeader.elements[0]; - expect(el1.forwardedFor, equals(const ForwardedIdentifier('a'))); - expect(el1.host, equals('x')); - expect(el1.by, isNull); - expect(el1.proto, isNull); - expect(el1.extensions, isNull); - - final el2 = parsedHeader.elements[1]; - expect(el2.by, equals(const ForwardedIdentifier('b'))); - expect(el2.proto, equals('y')); - expect(el2.forwardedFor, isNull); - expect(el2.host, isNull); - expect(el2.extensions, isNull); - - final el3 = parsedHeader.elements[2]; - expect(el3.forwardedFor, equals(const ForwardedIdentifier('c'))); - expect(el3.extensions, containsPair('ext', 'z')); - expect(el3.by, isNull); - expect(el3.host, isNull); - expect(el3.proto, isNull); - }); + expect(parsedHeader, isNotNull); + expect(parsedHeader.elements, hasLength(3)); + + final el1 = parsedHeader.elements[0]; + expect(el1.forwardedFor, equals(const ForwardedIdentifier('a'))); + expect(el1.host, equals('x')); + expect(el1.by, isNull); + expect(el1.proto, isNull); + expect(el1.extensions, isNull); + + final el2 = parsedHeader.elements[1]; + expect(el2.by, equals(const ForwardedIdentifier('b'))); + expect(el2.proto, equals('y')); + expect(el2.forwardedFor, isNull); + expect(el2.host, isNull); + expect(el2.extensions, isNull); + + final el3 = parsedHeader.elements[2]; + expect(el3.forwardedFor, equals(const ForwardedIdentifier('c'))); + expect(el3.extensions, containsPair('ext', 'z')); + expect(el3.by, isNull); + expect(el3.host, isNull); + expect(el3.proto, isNull); + }, + ); }); - group( - 'Given quoted values,', - skip: 'Quoted values are not handled correctly yet', - () { - test( - 'when parsing \'host="example.com, inc."\', ' - 'then host is parsed as "example.com, inc."', - () { - const headerValue = 'host="example.com, inc."'; - - final parsedHeader = ForwardedHeader.parse([headerValue]); - - expect(parsedHeader, isNotNull); - expect(parsedHeader.elements, hasLength(1)); - expect( - parsedHeader.elements.first.host, equals('example.com, inc.')); - }, - ); + group('Given quoted values,', skip: 'Quoted values are not handled correctly yet', () { + test('when parsing \'host="example.com, inc."\', ' + 'then host is parsed as "example.com, inc."', () { + const headerValue = 'host="example.com, inc."'; - test( - 'when parsing \'ext="value with ; semicolon"\', ' - 'then ext is parsed as "value with ; semicolon"', - () { - const headerValue = 'ext="value with ; semicolon"'; + final parsedHeader = ForwardedHeader.parse([headerValue]); - final parsedHeader = ForwardedHeader.parse([headerValue]); + expect(parsedHeader, isNotNull); + expect(parsedHeader.elements, hasLength(1)); + expect(parsedHeader.elements.first.host, equals('example.com, inc.')); + }); - expect(parsedHeader, isNotNull); - expect(parsedHeader.elements, hasLength(1)); - expect(parsedHeader.elements.first.extensions, isNotNull); - expect(parsedHeader.elements.first.extensions, - containsPair('ext', 'value with ; semicolon')); - }, - ); + test('when parsing \'ext="value with ; semicolon"\', ' + 'then ext is parsed as "value with ; semicolon"', () { + const headerValue = 'ext="value with ; semicolon"'; + + final parsedHeader = ForwardedHeader.parse([headerValue]); - test( - 'when parsing \'param1="val1"; param2="val2,still_val2"; param3="val3"\', ' - 'then param2 is currently split if simple split is used after unquoting for params (KNOWN LIMITATION).', - () { - const headerValueForElementSplit = - 'for="user,group", host=example.com'; - - final parsed = ForwardedHeader.parse([headerValueForElementSplit]); - - // Assert for element split (this demonstrates the current simplified behavior) - // String.split(',') on 'for="user,group", host=example.com' yields: - // 1. 'for="user' --> ForwardedElement(forwardedFor: ForwardedNode('user')) - // 2. 'group"' --> ForwardedElement() (empty, as 'group"' is not a valid pair) - // 3. ' host=example.com' --> ForwardedElement(host: 'example.com') - // This is because splitTrimAndFilterUnique (which relies on String.split(',')) is not quote-aware. - expect(parsed.elements, hasLength(1)); - final element = parsed.elements[0]; - expect( - element.forwardedFor, const ForwardedIdentifier('user,group')); - expect(element.host, 'example.com'); - expect(element.by, isNull); - expect(element.proto, isNull); - expect(element.extensions, isNull); - }, + expect(parsedHeader, isNotNull); + expect(parsedHeader.elements, hasLength(1)); + expect(parsedHeader.elements.first.extensions, isNotNull); + expect( + parsedHeader.elements.first.extensions, + containsPair('ext', 'value with ; semicolon'), ); - }, - ); + }); - group('Error Handling and Edge Cases,', () { test( - 'Given completely empty header values, ' - 'when parsing, ' - 'then FormatException is thrown by ForwardedHeader.parse.', + 'when parsing \'param1="val1"; param2="val2,still_val2"; param3="val3"\', ' + 'then param2 is currently split if simple split is used after unquoting for params (KNOWN LIMITATION).', () { - final headerValues = []; // No values - - expect(() => ForwardedHeader.parse(headerValues), - throwsA(isA())); + const headerValueForElementSplit = + 'for="user,group", host=example.com'; + + final parsed = ForwardedHeader.parse([headerValueForElementSplit]); + + // Assert for element split (this demonstrates the current simplified behavior) + // String.split(',') on 'for="user,group", host=example.com' yields: + // 1. 'for="user' --> ForwardedElement(forwardedFor: ForwardedNode('user')) + // 2. 'group"' --> ForwardedElement() (empty, as 'group"' is not a valid pair) + // 3. ' host=example.com' --> ForwardedElement(host: 'example.com') + // This is because splitTrimAndFilterUnique (which relies on String.split(',')) is not quote-aware. + expect(parsed.elements, hasLength(1)); + final element = parsed.elements[0]; + expect(element.forwardedFor, const ForwardedIdentifier('user,group')); + expect(element.host, 'example.com'); + expect(element.by, isNull); + expect(element.proto, isNull); + expect(element.extensions, isNull); }, ); + }); - test( - 'Given header values that are empty strings or only whitespace, ' - 'when parsing, ' - 'then FormatException is thrown', - () { - final headerValues = ['', ' ']; + group('Error Handling and Edge Cases,', () { + test('Given completely empty header values, ' + 'when parsing, ' + 'then FormatException is thrown by ForwardedHeader.parse.', () { + final headerValues = []; // No values + + expect( + () => ForwardedHeader.parse(headerValues), + throwsA(isA()), + ); + }); - // splitTrimAndFilterUnique will result in an empty list for ForwardedHeader.parse - expect(() => ForwardedHeader.parse(headerValues), - throwsA(isA())); - }, - ); + test('Given header values that are empty strings or only whitespace, ' + 'when parsing, ' + 'then FormatException is thrown', () { + final headerValues = ['', ' ']; - test( - 'Given malformed pairs like "for=", "keyonly", or "=value", ' - 'when parsing, ' - 'then they are currently ignored by the pair splitting logic.', - () { - const headerValue = 'for=1.2.3.4;keyonly;by=;proto=http;=novalue'; + // splitTrimAndFilterUnique will result in an empty list for ForwardedHeader.parse + expect( + () => ForwardedHeader.parse(headerValues), + throwsA(isA()), + ); + }); - final parsedHeader = ForwardedHeader.parse([headerValue]); + test('Given malformed pairs like "for=", "keyonly", or "=value", ' + 'when parsing, ' + 'then they are currently ignored by the pair splitting logic.', () { + const headerValue = 'for=1.2.3.4;keyonly;by=;proto=http;=novalue'; - // The pairs 'keyonly', 'by=', and '=novalue' will be skipped because parts.length != 2. - expect(parsedHeader, isNotNull); - expect(parsedHeader.elements, hasLength(1)); - final element = parsedHeader.elements.first; - expect(element.forwardedFor, - equals(const ForwardedIdentifier('1.2.3.4'))); - expect(element.proto, equals('http')); - expect(element.by, isNull); // 'by=' results in no 'by' node - expect(element.extensions, isNull); - }, - ); + final parsedHeader = ForwardedHeader.parse([headerValue]); + + // The pairs 'keyonly', 'by=', and '=novalue' will be skipped because parts.length != 2. + expect(parsedHeader, isNotNull); + expect(parsedHeader.elements, hasLength(1)); + final element = parsedHeader.elements.first; + expect( + element.forwardedFor, + equals(const ForwardedIdentifier('1.2.3.4')), + ); + expect(element.proto, equals('http')); + expect(element.by, isNull); // 'by=' results in no 'by' node + expect(element.extensions, isNull); + }); }); }); group('ForwardedHeader Encoding Logic', () { - test( - 'Given a ForwardedHeader with one element (for, proto, by), ' + test('Given a ForwardedHeader with one element (for, proto, by), ' 'when toStrings() is called, ' 'then it returns the correct string representation.', () { final element = ForwardedElement( @@ -299,8 +292,7 @@ void main() { expect(encoded, equals([expectedString])); }); - test( - 'Given a ForwardedHeader with an IPv6 address that needs quoting, ' + test('Given a ForwardedHeader with an IPv6 address that needs quoting, ' 'when toStrings() is called, ' 'then the IPv6 address is quoted.', () { final element = ForwardedElement( @@ -316,14 +308,16 @@ void main() { expect(encoded, equals([expectedString])); }); - test( - 'Given a ForwardedHeader with multiple elements, ' + test('Given a ForwardedHeader with multiple elements, ' 'when toStrings() is called, ' 'then elements are comma-separated.', () { - final element1 = - ForwardedElement(forwardedFor: const ForwardedIdentifier('client1')); + final element1 = ForwardedElement( + forwardedFor: const ForwardedIdentifier('client1'), + ); final element2 = ForwardedElement( - by: const ForwardedIdentifier('proxy1'), proto: 'https'); + by: const ForwardedIdentifier('proxy1'), + proto: 'https', + ); final header = ForwardedHeader([element1, element2]); const expectedString = 'for=client1, by=proxy1;proto=https'; @@ -332,8 +326,7 @@ void main() { expect(encoded, equals([expectedString])); }); - test( - 'Given a ForwardedHeader with extension parameters, ' + test('Given a ForwardedHeader with extension parameters, ' 'when toStrings() is called, ' 'then extensions are included.', () { final element = ForwardedElement( @@ -353,22 +346,23 @@ void main() { }); test( - 'Given a ForwardedHeader with a value requiring quoting (e.g. host with space), ' - 'when toStrings() is called, ' - 'then the value is quoted.', () { - final element = ForwardedElement(host: 'my server'); - final header = ForwardedHeader([element]); - const expectedString = 'host="my server"'; + 'Given a ForwardedHeader with a value requiring quoting (e.g. host with space), ' + 'when toStrings() is called, ' + 'then the value is quoted.', + () { + final element = ForwardedElement(host: 'my server'); + final header = ForwardedHeader([element]); + const expectedString = 'host="my server"'; - final encoded = header.toStrings(); + final encoded = header.toStrings(); - expect(encoded, equals([expectedString])); - }); + expect(encoded, equals([expectedString])); + }, + ); }); group('ForwardedNode Parsing Logic', () { - test( - 'Given "192.0.2.43", ' + test('Given "192.0.2.43", ' 'when ForwardedNode.parse is called, ' 'then identifier is "192.0.2.43" and port is null.', () { final node = ForwardedIdentifier.parse('192.0.2.43'); @@ -376,8 +370,7 @@ void main() { expect(node.port, isNull); }); - test( - 'Given "192.0.2.43:8080", ' + test('Given "192.0.2.43:8080", ' 'when ForwardedNode.parse is called, ' 'then identifier is "192.0.2.43" and port is "8080".', () { final node = ForwardedIdentifier.parse('192.0.2.43:8080'); @@ -385,8 +378,7 @@ void main() { expect(node.port, equals('8080')); }); - test( - 'Given "[2001:db8::1]", ' + test('Given "[2001:db8::1]", ' 'when ForwardedNode.parse is called, ' 'then identifier is "[2001:db8::1]" and port is null.', () { final node = ForwardedIdentifier.parse('[2001:db8::1]'); @@ -394,8 +386,7 @@ void main() { expect(node.port, isNull); }); - test( - 'Given "[2001:db8::1]:4711", ' + test('Given "[2001:db8::1]:4711", ' 'when ForwardedNode.parse is called, ' 'then identifier is "[2001:db8::1]" and port is "4711".', () { final node = ForwardedIdentifier.parse('[2001:db8::1]:4711'); @@ -403,8 +394,7 @@ void main() { expect(node.port, equals('4711')); }); - test( - 'Given "unknown", ' + test('Given "unknown", ' 'when ForwardedNode.parse is called, ' 'then identifier is "unknown" and port is null.', () { final node = ForwardedIdentifier.parse('unknown'); @@ -412,8 +402,7 @@ void main() { expect(node.port, isNull); }); - test( - 'Given "_obfuscated", ' + test('Given "_obfuscated", ' 'when ForwardedNode.parse is called, ' 'then identifier is "_obfuscated" and port is null.', () { final node = ForwardedIdentifier.parse('_obfuscated'); @@ -421,8 +410,7 @@ void main() { expect(node.port, isNull); }); - test( - 'Given "_obfuscated:_obfport", ' + test('Given "_obfuscated:_obfport", ' 'when ForwardedNode.parse is called, ' 'then identifier is "_obfuscated" and port is "_obfport".', () { final node = ForwardedIdentifier.parse('_obfuscated:_obfport'); @@ -431,16 +419,17 @@ void main() { }); test( - 'Given a malformed IPv6 like "[::1:8080" (missing closing bracket for address part), ' - 'when ForwardedNode.parse is called, ' - 'then it is treated as a simple identifier.', () { - // This doesn't match the strict IPv6 w/port pattern - final node = ForwardedIdentifier.parse('[::1:8080'); - expect(node.identifier, equals('[::1:8080')); - expect(node.port, isNull); - }); - test( - 'Given a malformed IPv6 with port like "[2001:db8::1]:8000:extra", ' + 'Given a malformed IPv6 like "[::1:8080" (missing closing bracket for address part), ' + 'when ForwardedNode.parse is called, ' + 'then it is treated as a simple identifier.', + () { + // This doesn't match the strict IPv6 w/port pattern + final node = ForwardedIdentifier.parse('[::1:8080'); + expect(node.identifier, equals('[::1:8080')); + expect(node.port, isNull); + }, + ); + test('Given a malformed IPv6 with port like "[2001:db8::1]:8000:extra", ' 'when ForwardedNode.parse is called, ' 'then it takes the whole string as identifier.', () { final node = ForwardedIdentifier.parse('[2001:db8::1]:8000:extra'); diff --git a/test/headers/typed/from_header_test.dart b/test/headers/typed/from_header_test.dart index 27a611ff..f3aecfbf 100644 --- a/test/headers/typed/from_header_test.dart +++ b/test/headers/typed/from_header_test.dart @@ -16,64 +16,55 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty From header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.from, - headers: {'from': ''}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); - - test( - 'when a From header with an invalid email format is passed ' - 'then the server responds with a bad request including a message that ' - 'states the email format is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.from, - headers: {'from': 'invalid-email-format'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid email format'), - ), + test('when an empty From header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.from, + headers: {'from': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); + ), + ); + }); - test( - 'when a From header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + test('when a From header with an invalid email format is passed ' + 'then the server responds with a bad request including a message that ' + 'states the email format is invalid', () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (final h) => h.from, headers: {'from': 'invalid-email-format'}, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid email format'), + ), + ), + ); + }); - expect(headers, isNotNull); - }, - ); + test('when a From header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'from': 'invalid-email-format'}, + ); + + expect(headers, isNotNull); + }); test( 'when a valid From header is passed then it should parse the email correctly', @@ -142,7 +133,7 @@ void main() { server: server, touchHeaders: (final h) => h.from, headers: { - 'from': 'user1@example.com, user2@example.com, user1@example.com' + 'from': 'user1@example.com, user2@example.com, user1@example.com', }, ); @@ -164,7 +155,7 @@ void main() { touchHeaders: (final h) => h.from, headers: { 'from': - 'user1@example.com, invalid-email-format, user2@example.com' + 'user1@example.com, invalid-email-format, user2@example.com', }, ), throwsA( @@ -179,18 +170,15 @@ void main() { ); }); - test( - 'when no From header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.from, - headers: {}, - ); + test('when no From header is passed then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.from, + headers: {}, + ); - expect(headers.from, isNull); - }, - ); + expect(headers.from, isNull); + }); }); group('Given a From header without validation', () { @@ -203,19 +191,16 @@ void main() { tearDown(() => server.close()); group('when an invalid From header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'from': 'invalid-email-format'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'from': 'invalid-email-format'}, + ); - expect(Headers.from[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.from, throwsInvalidHeader); - }, - ); + expect(Headers.from[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.from, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/if_match_header_test.dart b/test/headers/typed/if_match_header_test.dart index 7c07400c..84c3f6dc 100644 --- a/test/headers/typed/if_match_header_test.dart +++ b/test/headers/typed/if_match_header_test.dart @@ -16,27 +16,24 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty If-Match header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifMatch, - headers: {'if-match': ''}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), + test('when an empty If-Match header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifMatch, + headers: {'if-match': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); + ), + ); + }); test( 'when an If-Match header with an invalid ETag is passed then the server ' @@ -81,51 +78,42 @@ void main() { }, ); - test( - 'when an If-Match header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-match': 'invalid-etag'}, - ); + test('when an If-Match header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-match': 'invalid-etag'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); - test( - 'when an If-Match header with a single valid ETag is passed then it ' - 'should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifMatch, - headers: {'if-match': '"123456"'}, - ); + test('when an If-Match header with a single valid ETag is passed then it ' + 'should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifMatch, + headers: {'if-match': '"123456"'}, + ); - expect(headers.ifMatch?.etags.length, equals(1)); - expect(headers.ifMatch?.etags.first.value, equals('123456')); - expect(headers.ifMatch?.etags.first.isWeak, isFalse); - }, - ); + expect(headers.ifMatch?.etags.length, equals(1)); + expect(headers.ifMatch?.etags.first.value, equals('123456')); + expect(headers.ifMatch?.etags.first.isWeak, isFalse); + }); - test( - 'when an If-Match header with a wildcard (*) is passed then it ' - 'should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifMatch, - headers: {'if-match': '*'}, - ); + test('when an If-Match header with a wildcard (*) is passed then it ' + 'should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifMatch, + headers: {'if-match': '*'}, + ); - expect(headers.ifMatch?.isWildcard, isTrue); - expect(headers.ifMatch?.etags, isEmpty); - }, - ); + expect(headers.ifMatch?.isWildcard, isTrue); + expect(headers.ifMatch?.etags, isEmpty); + }); test( 'when no If-Match header is passed then it should return null', @@ -141,39 +129,30 @@ void main() { ); group('when multiple ETags are passed', () { - test( - 'ETags are passed then they should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifMatch, - headers: {'if-match': '"123", "456", "789"'}, - ); + test('ETags are passed then they should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifMatch, + headers: {'if-match': '"123", "456", "789"'}, + ); - expect(headers.ifMatch?.etags.length, equals(3)); - expect( - headers.ifMatch?.etags.map((final e) => e.value).toList(), - equals(['123', '456', '789']), - ); - }, - ); + expect(headers.ifMatch?.etags.length, equals(3)); + expect( + headers.ifMatch?.etags.map((final e) => e.value).toList(), + equals(['123', '456', '789']), + ); + }); - test( - 'with W/ weak validator prefix should be accepted', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifMatch, - headers: {'if-match': 'W/"123", W/"456"'}, - ); + test('with W/ weak validator prefix should be accepted', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifMatch, + headers: {'if-match': 'W/"123", W/"456"'}, + ); - expect(headers.ifMatch?.etags.length, equals(2)); - expect( - headers.ifMatch?.etags.every((final e) => e.isWeak), - isTrue, - ); - }, - ); + expect(headers.ifMatch?.etags.length, equals(2)); + expect(headers.ifMatch?.etags.every((final e) => e.isWeak), isTrue); + }); test( 'with extra whitespace are passed then they should parse correctly', @@ -192,23 +171,20 @@ void main() { }, ); - test( - 'with duplicate values are passed then they should parse correctly ' - 'and remove duplicates', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifMatch, - headers: {'if-match': '"123", "456", "789", "123"'}, - ); + test('with duplicate values are passed then they should parse correctly ' + 'and remove duplicates', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifMatch, + headers: {'if-match': '"123", "456", "789", "123"'}, + ); - expect(headers.ifMatch?.etags.length, equals(3)); - expect( - headers.ifMatch?.etags.map((final e) => e.value).toList(), - equals(['123', '456', '789']), - ); - }, - ); + expect(headers.ifMatch?.etags.length, equals(3)); + expect( + headers.ifMatch?.etags.map((final e) => e.value).toList(), + equals(['123', '456', '789']), + ); + }); }); }); @@ -222,19 +198,16 @@ void main() { tearDown(() => server.close()); group('when an invalid If-Match header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-match': 'invalid-etag'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-match': 'invalid-etag'}, + ); - expect(Headers.ifMatch[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.ifMatch, throwsInvalidHeader); - }, - ); + expect(Headers.ifMatch[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.ifMatch, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/if_none_match_header_test.dart b/test/headers/typed/if_none_match_header_test.dart index 8c511811..a749c089 100644 --- a/test/headers/typed/if_none_match_header_test.dart +++ b/test/headers/typed/if_none_match_header_test.dart @@ -81,20 +81,17 @@ void main() { }, ); - test( - 'when an If-None-Match header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-none-match': 'invalid-etag'}, - ); + test('when an If-None-Match header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-none-match': 'invalid-etag'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when an If-None-Match header with a single valid ETag is passed then it ' @@ -112,20 +109,17 @@ void main() { }, ); - test( - 'when an If-None-Match header with a wildcard (*) is passed then it ' - 'should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifNoneMatch, - headers: {'if-none-match': '*'}, - ); + test('when an If-None-Match header with a wildcard (*) is passed then it ' + 'should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifNoneMatch, + headers: {'if-none-match': '*'}, + ); - expect(headers.ifNoneMatch?.isWildcard, isTrue); - expect(headers.ifNoneMatch?.etags, isEmpty); - }, - ); + expect(headers.ifNoneMatch?.isWildcard, isTrue); + expect(headers.ifNoneMatch?.etags, isEmpty); + }); test( 'when no If-None-Match header is passed then it should default to null', @@ -141,39 +135,30 @@ void main() { ); group('when multiple ETags are passed', () { - test( - 'then they should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifNoneMatch, - headers: {'if-none-match': '"123", "456", "789"'}, - ); + test('then they should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifNoneMatch, + headers: {'if-none-match': '"123", "456", "789"'}, + ); - expect(headers.ifNoneMatch?.etags.length, equals(3)); - expect( - headers.ifNoneMatch?.etags.map((final e) => e.value).toList(), - equals(['123', '456', '789']), - ); - }, - ); + expect(headers.ifNoneMatch?.etags.length, equals(3)); + expect( + headers.ifNoneMatch?.etags.map((final e) => e.value).toList(), + equals(['123', '456', '789']), + ); + }); - test( - 'with W/ weak validator prefix should be accepted', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifNoneMatch, - headers: {'if-none-match': 'W/"123", W/"456"'}, - ); + test('with W/ weak validator prefix should be accepted', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifNoneMatch, + headers: {'if-none-match': 'W/"123", W/"456"'}, + ); - expect(headers.ifNoneMatch?.etags.length, equals(2)); - expect( - headers.ifNoneMatch?.etags.every((final e) => e.isWeak), - isTrue, - ); - }, - ); + expect(headers.ifNoneMatch?.etags.length, equals(2)); + expect(headers.ifNoneMatch?.etags.every((final e) => e.isWeak), isTrue); + }); test( 'with extra whitespace are passed then they should parse correctly', @@ -192,23 +177,20 @@ void main() { }, ); - test( - 'with duplicate values are passed then they should parse correctly ' - 'and remove duplicates', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifNoneMatch, - headers: {'if-none-match': '"123", "456", "789", "123"'}, - ); + test('with duplicate values are passed then they should parse correctly ' + 'and remove duplicates', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifNoneMatch, + headers: {'if-none-match': '"123", "456", "789", "123"'}, + ); - expect(headers.ifNoneMatch?.etags.length, equals(3)); - expect( - headers.ifNoneMatch?.etags.map((final e) => e.value).toList(), - equals(['123', '456', '789']), - ); - }, - ); + expect(headers.ifNoneMatch?.etags.length, equals(3)); + expect( + headers.ifNoneMatch?.etags.map((final e) => e.value).toList(), + equals(['123', '456', '789']), + ); + }); }); }); @@ -222,19 +204,16 @@ void main() { tearDown(() => server.close()); group('when an invalid If-None-Match header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-none-match': 'invalid-etag'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-none-match': 'invalid-etag'}, + ); - expect(Headers.ifNoneMatch[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.ifNoneMatch, throwsInvalidHeader); - }, - ); + expect(Headers.ifNoneMatch[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.ifNoneMatch, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/if_range_header_test.dart b/test/headers/typed/if_range_header_test.dart index 89f6029b..bf339e0d 100644 --- a/test/headers/typed/if_range_header_test.dart +++ b/test/headers/typed/if_range_header_test.dart @@ -17,27 +17,24 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty If-Range header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.ifRange, - headers: {'if-range': ''}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), + test('when an empty If-Range header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.ifRange, + headers: {'if-range': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); + ), + ); + }); test( 'when an invalid ETag format is passed then the server responds with a ' @@ -60,20 +57,17 @@ void main() { }, ); - test( - 'when an If-Range header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-range': 'invalid-value'}, - ); + test('when an If-Range header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-range': 'invalid-value'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when an If-Range header with a valid ETag is passed then it should parse correctly', @@ -146,19 +140,16 @@ void main() { tearDown(() => server.close()); group('when an invalid If-Range header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'if-range': 'invalid-value'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'if-range': 'invalid-value'}, + ); - expect(Headers.ifRange[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.ifRange, throwsInvalidHeader); - }, - ); + expect(Headers.ifRange[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.ifRange, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/permissions_policy_header_test.dart b/test/headers/typed/permissions_policy_header_test.dart index 92f39e6c..4cec263c 100644 --- a/test/headers/typed/permissions_policy_header_test.dart +++ b/test/headers/typed/permissions_policy_header_test.dart @@ -26,29 +26,28 @@ void main() { touchHeaders: (final h) => h.permissionsPolicy, headers: {'permissions-policy': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); - test( - 'when a Permissions-Policy header with an empty value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'permissions-policy': ''}, - ); + test('when a Permissions-Policy header with an empty value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'permissions-policy': ''}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid Permissions-Policy header is passed then it should parse the policies correctly', @@ -75,7 +74,7 @@ void main() { server: server, headers: { 'permissions-policy': - 'geolocation=(self), camera=(self "https://example.com")' + 'geolocation=(self), camera=(self "https://example.com")', }, touchHeaders: (final h) => h.permissionsPolicy, ); @@ -113,20 +112,16 @@ void main() { tearDown(() => server.close()); group('when an empty Permissions-Policy header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'permissions-policy': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'permissions-policy': ''}, + ); - expect( - Headers.permissionsPolicy[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.permissionsPolicy, throwsInvalidHeader); - }, - ); + expect(Headers.permissionsPolicy[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.permissionsPolicy, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/proxy_authenticate_header_test.dart b/test/headers/typed/proxy_authenticate_header_test.dart index ec22ccb5..83c48de7 100644 --- a/test/headers/typed/proxy_authenticate_header_test.dart +++ b/test/headers/typed/proxy_authenticate_header_test.dart @@ -25,29 +25,28 @@ void main() { touchHeaders: (final h) => h.proxyAuthenticate, headers: {'proxy-authenticate': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); - test( - 'when a Proxy-Authenticate header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'proxy-authenticate': 'Test'}, - ); + test('when a Proxy-Authenticate header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'proxy-authenticate': 'Test'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); group('when Basic authentication', () { test('with realm parameter should parse scheme correctly', () async { @@ -80,7 +79,7 @@ void main() { test('should parse scheme correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'proxy-authenticate': 'Digest realm="Proxy Authentication Required"', @@ -93,7 +92,7 @@ void main() { test('should parse realm parameter correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'proxy-authenticate': 'Digest realm="Proxy Authentication Required"', @@ -111,10 +110,10 @@ void main() { test('should parse qop parameter correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'proxy-authenticate': - 'Digest realm="Proxy Authentication Required", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"' + 'Digest realm="Proxy Authentication Required", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"', }, ); @@ -129,10 +128,10 @@ void main() { test('should parse nonce parameter correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'proxy-authenticate': - 'Digest realm="Proxy Authentication Required", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"' + 'Digest realm="Proxy Authentication Required", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"', }, ); @@ -147,10 +146,10 @@ void main() { test('should parse opaque parameter correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'proxy-authenticate': - 'Digest realm="Proxy Authentication Required", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"' + 'Digest realm="Proxy Authentication Required", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"', }, ); @@ -191,7 +190,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'proxy-authenticate': 'InvalidHeader'}, ); diff --git a/test/headers/typed/proxy_authorization_header_test.dart b/test/headers/typed/proxy_authorization_header_test.dart index 911c013e..3b318d9a 100644 --- a/test/headers/typed/proxy_authorization_header_test.dart +++ b/test/headers/typed/proxy_authorization_header_test.dart @@ -18,44 +18,36 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty Proxy-Authorization header is passed then the server ' - 'responds with a bad request including a message that states the header ' - 'value cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': ''}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), + test('when an empty Proxy-Authorization header is passed then the server ' + 'responds with a bad request including a message that states the header ' + 'value cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); + ), + ); + }); - test( - 'when a Proxy-Authorization header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: { - 'proxy-authorization': 'invalid-proxy-authorization-format' - }, - ); + test('when a Proxy-Authorization header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'proxy-authorization': 'invalid-proxy-authorization-format'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when no Proxy-Authorization header is passed then it should default to null', @@ -134,24 +126,21 @@ void main() { }, ); - test( - 'when a Basic token is passed then it should parse the credentials ' - 'correctly', - () async { - final credentials = base64Encode(utf8.encode('user:pass')); - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Basic $credentials'}, - ); - expect( - headers.proxyAuthorization, - isA() - .having((final auth) => auth.username, 'username', 'user') - .having((final auth) => auth.password, 'password', 'pass'), - ); - }, - ); + test('when a Basic token is passed then it should parse the credentials ' + 'correctly', () async { + final credentials = base64Encode(utf8.encode('user:pass')); + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Basic $credentials'}, + ); + expect( + headers.proxyAuthorization, + isA() + .having((final auth) => auth.username, 'username', 'user') + .having((final auth) => auth.password, 'password', 'pass'), + ); + }); }); group('and a Digest Authorization header', () { @@ -177,269 +166,236 @@ void main() { ); group('when a Digest token is passed with missing', () { - test( - '"username" then the server responds with a bad request including ' - 'a message that states the username is required', - () async { - const digestValue = - 'missingUsername="user", realm="realm", nonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Username is required and cannot be empty'), - ), - ), - ); - }, - ); - test( - '"realm" then the server responds with a bad request including ' - 'a message that states the realm is required', - () async { - const digestValue = - 'username="user", missingRealm="realm", nonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Realm is required and cannot be empty'), - ), - ), - ); - }, - ); + test('"username" then the server responds with a bad request including ' + 'a message that states the username is required', () async { + const digestValue = + 'missingUsername="user", realm="realm", nonce="nonce", uri="/", response="response"'; - test( - '"nonce" then the server responds with a bad request including ' - 'a message that states the nonce is required', - () async { - const digestValue = - 'username="user", realm="realm", missingNonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Nonce is required and cannot be empty'), - ), + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Username is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); + test('"realm" then the server responds with a bad request including ' + 'a message that states the realm is required', () async { + const digestValue = + 'username="user", missingRealm="realm", nonce="nonce", uri="/", response="response"'; - test( - '"uri" then the server responds with a bad request including ' - 'a message that states the uri is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="nonce", missingUri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Realm is required and cannot be empty'), ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('URI is required and cannot be empty'), - ), + ), + ); + }); + + test('"nonce" then the server responds with a bad request including ' + 'a message that states the nonce is required', () async { + const digestValue = + 'username="user", realm="realm", missingNonce="nonce", uri="/", response="response"'; + + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Nonce is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); - test( - '"response" then the server responds with a bad request including ' - 'a message that states the response is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="nonce", uri="/", missingResponse="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, + test('"uri" then the server responds with a bad request including ' + 'a message that states the uri is required', () async { + const digestValue = + 'username="user", realm="realm", nonce="nonce", missingUri="/", response="response"'; + + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('URI is required and cannot be empty'), ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Response is required and cannot be empty'), - ), + ), + ); + }); + + test('"response" then the server responds with a bad request including ' + 'a message that states the response is required', () async { + const digestValue = + 'username="user", realm="realm", nonce="nonce", uri="/", missingResponse="response"'; + + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Response is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); }); group('when a Digest token is passed with empty', () { - test( - '"username" then the server responds with a bad request including ' - 'a message that states the username is required', - () async { - const digestValue = - 'username="", realm="realm", nonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Username is required and cannot be empty'), - ), - ), - ); - }, - ); - test( - '"realm" then the server responds with a bad request including ' - 'a message that states the realm is required', - () async { - const digestValue = - 'username="user", realm="", nonce="nonce", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Realm is required and cannot be empty'), - ), - ), - ); - }, - ); + test('"username" then the server responds with a bad request including ' + 'a message that states the username is required', () async { + const digestValue = + 'username="", realm="realm", nonce="nonce", uri="/", response="response"'; - test( - '"nonce" then the server responds with a bad request including ' - 'a message that states the nonce is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="", uri="/", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Nonce is required and cannot be empty'), - ), + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Username is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); + test('"realm" then the server responds with a bad request including ' + 'a message that states the realm is required', () async { + const digestValue = + 'username="user", realm="", nonce="nonce", uri="/", response="response"'; - test( - '"uri" then the server responds with a bad request including ' - 'a message that states the uri is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="nonce", uri="", response="response"'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('URI is required and cannot be empty'), - ), + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Realm is required and cannot be empty'), ), - ); - }, - ); + ), + ); + }); - test( - '"response" then the server responds with a bad request including ' - 'a message that states the response is required', - () async { - const digestValue = - 'username="user", realm="realm", nonce="nonce", uri="/", response=""'; - - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Response is required and cannot be empty'), - ), + test('"nonce" then the server responds with a bad request including ' + 'a message that states the nonce is required', () async { + const digestValue = + 'username="user", realm="realm", nonce="", uri="/", response="response"'; + + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Nonce is required and cannot be empty'), ), - ); - }, - ); - }); + ), + ); + }); - test( - 'when a Digest token is passed with all required parameters then it ' - 'should parse the credentials correctly', - () async { + test('"uri" then the server responds with a bad request including ' + 'a message that states the uri is required', () async { const digestValue = - 'username="user", realm="realm", nonce="nonce", uri="/", response="response"'; - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.proxyAuthorization, - headers: {'proxy-authorization': 'Digest $digestValue'}, + 'username="user", realm="realm", nonce="nonce", uri="", response="response"'; + + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('URI is required and cannot be empty'), + ), + ), ); + }); + + test('"response" then the server responds with a bad request including ' + 'a message that states the response is required', () async { + const digestValue = + 'username="user", realm="realm", nonce="nonce", uri="/", response=""'; + expect( - headers.proxyAuthorization, - isA() - .having((final auth) => auth.username, 'username', 'user') - .having((final auth) => auth.realm, 'realm', 'realm') - .having((final auth) => auth.nonce, 'nonce', 'nonce') - .having((final auth) => auth.uri, 'uri', '/') - .having( - (final auth) => auth.response, 'response', 'response')); - }, - ); + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Response is required and cannot be empty'), + ), + ), + ); + }); + }); + + test('when a Digest token is passed with all required parameters then it ' + 'should parse the credentials correctly', () async { + const digestValue = + 'username="user", realm="realm", nonce="nonce", uri="/", response="response"'; + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.proxyAuthorization, + headers: {'proxy-authorization': 'Digest $digestValue'}, + ); + expect( + headers.proxyAuthorization, + isA() + .having((final auth) => auth.username, 'username', 'user') + .having((final auth) => auth.realm, 'realm', 'realm') + .having((final auth) => auth.nonce, 'nonce', 'nonce') + .having((final auth) => auth.uri, 'uri', '/') + .having((final auth) => auth.response, 'response', 'response'), + ); + }); test( 'when a Digest token is passed then it should parse the credentials correctly', @@ -480,36 +436,34 @@ void main() { tearDown(() => server.close()); group('when an empty Proxy-Authorization header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'proxy-authorization': ''}, - ); - expect( - Headers.proxyAuthorization[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.proxyAuthorization, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'proxy-authorization': ''}, + ); + expect( + Headers.proxyAuthorization[headers].valueOrNullIfInvalid, + isNull, + ); + expect(() => headers.proxyAuthorization, throwsInvalidHeader); + }); }); group('when an invalid Proxy-Authorization header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'proxy-authorization': 'InvalidFormat'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'proxy-authorization': 'InvalidFormat'}, + ); - expect( - Headers.proxyAuthorization[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.proxyAuthorization, throwsInvalidHeader); - }, - ); + expect( + Headers.proxyAuthorization[headers].valueOrNullIfInvalid, + isNull, + ); + expect(() => headers.proxyAuthorization, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/range_header_test.dart b/test/headers/typed/range_header_test.dart index 93edad9e..d8b8d091 100644 --- a/test/headers/typed/range_header_test.dart +++ b/test/headers/typed/range_header_test.dart @@ -16,27 +16,24 @@ void main() { tearDown(() => server.close()); - test( - 'when a Range header with an empty value is passed then the server ' - 'responds with a bad request including a message that states the ' - 'header value cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.range, - headers: {'range': ''}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), + test('when a Range header with an empty value is passed then the server ' + 'responds with a bad request including a message that states the ' + 'header value cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.range, + headers: {'range': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); + ), + ); + }); test( 'when a range with invalid format is passed then the server responds with a ' @@ -80,37 +77,31 @@ void main() { }, ); - test( - 'when a Range header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'range': 'invalid-value'}, - ); + test('when a Range header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'range': 'invalid-value'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); - test( - 'when a Range header with a single valid range is passed then it ' - 'should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.range, - headers: {'range': 'bytes=0-499'}, - ); + test('when a Range header with a single valid range is passed then it ' + 'should parse correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.range, + headers: {'range': 'bytes=0-499'}, + ); - expect(headers.range?.unit, equals('bytes')); - expect(headers.range?.ranges.length, equals(1)); - expect(headers.range?.ranges.first.start, equals(0)); - expect(headers.range?.ranges.first.end, equals(499)); - }, - ); + expect(headers.range?.unit, equals('bytes')); + expect(headers.range?.ranges.length, equals(1)); + expect(headers.range?.ranges.first.start, equals(0)); + expect(headers.range?.ranges.first.end, equals(499)); + }); test( 'when a Range header with a range that only has a start is passed then it ' @@ -146,41 +137,35 @@ void main() { }, ); - test( - 'when no Range header is passed then it should return null', - () async { + test('when no Range header is passed then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.range, + headers: {}, + ); + + expect(headers.range, isNull); + }); + + group('when multiple Range headers are passed', () { + test('then they should parse correctly', () async { final headers = await getServerRequestHeaders( server: server, touchHeaders: (final h) => h.range, - headers: {}, + headers: {'range': 'bytes=0-499, 500-999, 1000-'}, ); - expect(headers.range, isNull); - }, - ); - - group('when multiple Range headers are passed', () { - test( - 'then they should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.range, - headers: {'range': 'bytes=0-499, 500-999, 1000-'}, - ); - - expect(headers.range?.unit, equals('bytes')); - expect(headers.range?.ranges.length, equals(3)); + expect(headers.range?.unit, equals('bytes')); + expect(headers.range?.ranges.length, equals(3)); - final ranges = headers.range!.ranges; - expect(ranges[0].start, equals(0)); - expect(ranges[0].end, equals(499)); - expect(ranges[1].start, equals(500)); - expect(ranges[1].end, equals(999)); - expect(ranges[2].start, equals(1000)); - expect(ranges[2].end, isNull); - }, - ); + final ranges = headers.range!.ranges; + expect(ranges[0].start, equals(0)); + expect(ranges[0].end, equals(499)); + expect(ranges[1].start, equals(500)); + expect(ranges[1].end, equals(999)); + expect(ranges[2].start, equals(1000)); + expect(ranges[2].end, isNull); + }); test( 'with extra whitespace are passed then they should parse correctly', @@ -208,19 +193,16 @@ void main() { tearDown(() => server.close()); group('when an invalid Range header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'range': 'invalid-range'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'range': 'invalid-range'}, + ); - expect(Headers.range[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.range, throwsInvalidHeader); - }, - ); + expect(Headers.range[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.range, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/referrer_policy_header_test.dart b/test/headers/typed/referrer_policy_header_test.dart index 5dfbd1ad..f1ea0eca 100644 --- a/test/headers/typed/referrer_policy_header_test.dart +++ b/test/headers/typed/referrer_policy_header_test.dart @@ -26,11 +26,13 @@ void main() { touchHeaders: (final h) => h.referrerPolicy, headers: {'referrer-policy': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -45,29 +47,28 @@ void main() { touchHeaders: (final h) => h.referrerPolicy, headers: {'referrer-policy': 'invalid-value'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid value'), + ), + ), ); }, ); - test( - 'when a Referrer-Policy header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'referrer-policy': 'invalid-value'}, - ); + test('when a Referrer-Policy header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'referrer-policy': 'invalid-value'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a valid Referrer-Policy header is passed then it should parse the policy correctly', @@ -106,19 +107,16 @@ void main() { tearDown(() => server.close()); group('when an empty Referrer-Policy header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'referrer-policy': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'referrer-policy': ''}, + ); - expect(Headers.referrerPolicy[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.referrerPolicy, throwsInvalidHeader); - }, - ); + expect(Headers.referrerPolicy[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.referrerPolicy, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/retry_after_header_test.dart b/test/headers/typed/retry_after_header_test.dart index c52dee84..66907843 100644 --- a/test/headers/typed/retry_after_header_test.dart +++ b/test/headers/typed/retry_after_header_test.dart @@ -27,29 +27,28 @@ void main() { touchHeaders: (final h) => h.retryAfter, headers: {'retry-after': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); - test( - 'when a Retry-After header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'retry-after': 'invalid'}, - ); + test('when a Retry-After header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'retry-after': 'invalid'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); group('when the header contains a delay in seconds', () { test('then it should parse a valid positive integer correctly', () async { @@ -79,11 +78,13 @@ void main() { touchHeaders: (final h) => h.retryAfter, headers: {'retry-after': '-120'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Delay cannot be negative'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Delay cannot be negative'), + ), + ), ); }); @@ -94,11 +95,13 @@ void main() { touchHeaders: (final h) => h.retryAfter, headers: {'retry-after': '120.5'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid date format'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid date format'), + ), + ), ); }); }); @@ -137,11 +140,13 @@ void main() { touchHeaders: (final h) => h.retryAfter, headers: {'retry-after': '2015-10-21'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid date format'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid date format'), + ), + ), ); }); }); @@ -174,7 +179,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'retry-after': 'invalid'}, ); diff --git a/test/headers/typed/sec_fetch_dest_header_test.dart b/test/headers/typed/sec_fetch_dest_header_test.dart index 61af9a29..a9e61441 100644 --- a/test/headers/typed/sec_fetch_dest_header_test.dart +++ b/test/headers/typed/sec_fetch_dest_header_test.dart @@ -26,11 +26,13 @@ void main() { touchHeaders: (final h) => h.secFetchDest, headers: {'sec-fetch-dest': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -45,28 +47,27 @@ void main() { touchHeaders: (final h) => h.secFetchDest, headers: {'sec-fetch-dest': 'custom-destination'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid value'), + ), + ), ); }, ); - test( - 'when a Sec-Fetch-Dest header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'sec-fetch-dest': 'custom-destination'}, - ); - expect(headers, isNotNull); - }, - ); + test('when a Sec-Fetch-Dest header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'sec-fetch-dest': 'custom-destination'}, + ); + expect(headers, isNotNull); + }); test( 'when a valid Sec-Fetch-Dest header is passed then it should parse the destination correctly', @@ -105,18 +106,15 @@ void main() { tearDown(() => server.close()); group('When an empty Sec-Fetch-Dest header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {}, + ); - expect(headers.secFetchDest, isNull); - }, - ); + expect(headers.secFetchDest, isNull); + }); }); }); } diff --git a/test/headers/typed/sec_fetch_mode_header_test.dart b/test/headers/typed/sec_fetch_mode_header_test.dart index ed0ef9e3..ef19240f 100644 --- a/test/headers/typed/sec_fetch_mode_header_test.dart +++ b/test/headers/typed/sec_fetch_mode_header_test.dart @@ -26,11 +26,13 @@ void main() { touchHeaders: (final h) => h.secFetchMode, headers: {'sec-fetch-mode': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -45,28 +47,27 @@ void main() { touchHeaders: (final h) => h.secFetchMode, headers: {'sec-fetch-mode': 'custom-mode'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid value'), + ), + ), ); }, ); - test( - 'when a Sec-Fetch-Mode header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'sec-fetch-mode': 'custom-mode'}, - ); - expect(headers, isNotNull); - }, - ); + test('when a Sec-Fetch-Mode header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'sec-fetch-mode': 'custom-mode'}, + ); + expect(headers, isNotNull); + }); test( 'when a valid Sec-Fetch-Mode header is passed then it should parse the mode correctly', @@ -105,18 +106,15 @@ void main() { tearDown(() => server.close()); group('When an empty Sec-Fetch-Mode header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {}, + ); - expect(headers.secFetchMode, isNull); - }, - ); + expect(headers.secFetchMode, isNull); + }); }); }); } diff --git a/test/headers/typed/sec_fetch_site_header_test.dart b/test/headers/typed/sec_fetch_site_header_test.dart index af1f3c5d..3f8f706e 100644 --- a/test/headers/typed/sec_fetch_site_header_test.dart +++ b/test/headers/typed/sec_fetch_site_header_test.dart @@ -26,11 +26,13 @@ void main() { touchHeaders: (final h) => h.secFetchSite, headers: {'sec-fetch-site': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -45,28 +47,27 @@ void main() { touchHeaders: (final h) => h.secFetchSite, headers: {'sec-fetch-site': 'custom-site'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid value'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid value'), + ), + ), ); }, ); - test( - 'when a Sec-Fetch-Site header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'sec-fetch-site': 'custom-site'}, - ); - expect(headers, isNotNull); - }, - ); + test('when a Sec-Fetch-Site header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'sec-fetch-site': 'custom-site'}, + ); + expect(headers, isNotNull); + }); test( 'when a valid Sec-Fetch-Site header is passed then it should parse the site correctly', @@ -105,18 +106,15 @@ void main() { tearDown(() => server.close()); group('When an empty Sec-Fetch-Site header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {}, + ); - expect(headers.secFetchSite, isNull); - }, - ); + expect(headers.secFetchSite, isNull); + }); }); }); } diff --git a/test/headers/typed/set_cookie_header_test.dart b/test/headers/typed/set_cookie_header_test.dart index 1ec29a72..516bc0cd 100644 --- a/test/headers/typed/set_cookie_header_test.dart +++ b/test/headers/typed/set_cookie_header_test.dart @@ -45,14 +45,16 @@ void main() { server: server, touchHeaders: (final h) => h.setCookie, headers: { - 'set-cookie': 'sessionId=abc123; Max-Age=3600; Max-Age=7200' + 'set-cookie': 'sessionId=abc123; Max-Age=3600; Max-Age=7200', }, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Supplied multiple Max-Age attributes'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Supplied multiple Max-Age attributes'), + ), + ), ); }, ); @@ -68,11 +70,13 @@ void main() { touchHeaders: (final h) => h.setCookie, headers: {'set-cookie': 'sessionId=abc123; SameSite=Invalid'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid SameSite attribute'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid SameSite attribute'), + ), + ), ); }, ); @@ -162,20 +166,17 @@ void main() { }, ); - test( - 'when a Set-Cookie header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'set-cookie': 'userId=42\x7F'}, - ); + test('when a Set-Cookie header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'set-cookie': 'userId=42\x7F'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a Set-Cookie header with an empty name is passed then it should parse the cookie correctly', @@ -196,10 +197,10 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'set-cookie': - 'sessionId=abc123; Expires=Wed, 21 Oct 2025 07:28:00 GMT; Max-Age=3600; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict' + 'sessionId=abc123; Expires=Wed, 21 Oct 2025 07:28:00 GMT; Max-Age=3600; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict', }, ); @@ -246,7 +247,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'set-cookie': 'sessionId=abc123; invalidCookie'}, ); diff --git a/test/headers/typed/strict_transport_security_header_test.dart b/test/headers/typed/strict_transport_security_header_test.dart index 8f2b0e99..9441087f 100644 --- a/test/headers/typed/strict_transport_security_header_test.dart +++ b/test/headers/typed/strict_transport_security_header_test.dart @@ -7,152 +7,155 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Strict-Transport-Security header with validation', - () { - late RelicServer server; + group('Given a Strict-Transport-Security header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); - - test( - 'when an empty Strict-Transport-Security header is passed then the server should respond with a bad request ' - 'including a message that states the value cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.strictTransportSecurity, - headers: {'strict-transport-security': ''}, - ), - throwsA(isA().having( + tearDown(() => server.close()); + + test( + 'when an empty Strict-Transport-Security header is passed then the server should respond with a bad request ' + 'including a message that states the value cannot be empty', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.strictTransportSecurity, + headers: {'strict-transport-security': ''}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Value cannot be empty'), - )), - ); - }, - ); - - test( - 'when a Strict-Transport-Security header with invalid max-age is passed ' - 'then the server should respond with a bad request including a message ' - 'that states the max-age directive is missing or invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.strictTransportSecurity, - headers: {'strict-transport-security': 'max-age=abc'}, ), - throwsA(isA().having( + ), + ); + }, + ); + + test( + 'when a Strict-Transport-Security header with invalid max-age is passed ' + 'then the server should respond with a bad request including a message ' + 'that states the max-age directive is missing or invalid', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.strictTransportSecurity, + headers: {'strict-transport-security': 'max-age=abc'}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Max-age directive is missing or invalid'), - )), - ); - }, - ); - - test( - 'when a Strict-Transport-Security header without max-age is passed then ' - 'the server should respond with a bad request including a message that ' - 'states the max-age directive is missing or invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.strictTransportSecurity, - headers: {'strict-transport-security': 'includeSubDomains'}, ), - throwsA(isA().having( + ), + ); + }, + ); + + test( + 'when a Strict-Transport-Security header without max-age is passed then ' + 'the server should respond with a bad request including a message that ' + 'states the max-age directive is missing or invalid', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.strictTransportSecurity, + headers: {'strict-transport-security': 'includeSubDomains'}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Max-age directive is missing or invalid'), - )), - ); - }, - ); - - test( - 'when a Strict-Transport-Security header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'strict-transport-security': 'max-age=abc'}, - ); - - expect(headers, isNotNull); - }, - ); - - test( - 'when a valid Strict-Transport-Security header is passed then it should parse the directives correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: { - 'strict-transport-security': 'max-age=31536000; includeSubDomains' - }, - ); - - final hsts = headers.strictTransportSecurity; - expect(hsts?.maxAge, equals(31536000)); - expect(hsts?.includeSubDomains, isTrue); - }, - ); - - test( - 'when a Strict-Transport-Security header without includeSubDomains is passed then it should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.strictTransportSecurity, - headers: {'strict-transport-security': 'max-age=31536000'}, - ); - - final hsts = headers.strictTransportSecurity; - expect(hsts?.maxAge, equals(31536000)); - expect(hsts?.includeSubDomains, isFalse); - }, - ); - - test( - 'when a Strict-Transport-Security header with preload is passed then it should parse correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.strictTransportSecurity, - headers: {'strict-transport-security': 'max-age=31536000; preload'}, - ); - - final hsts = headers.strictTransportSecurity; - expect(hsts?.maxAge, equals(31536000)); - expect(hsts?.preload, isTrue); - }, - ); - - test( - 'when no Strict-Transport-Security header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.strictTransportSecurity, - headers: {}, - ); - - expect(headers.strictTransportSecurity, isNull); - }, - ); - }, - ); + ), + ), + ); + }, + ); + + test( + 'when a Strict-Transport-Security header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'strict-transport-security': 'max-age=abc'}, + ); + + expect(headers, isNotNull); + }, + ); + + test( + 'when a valid Strict-Transport-Security header is passed then it should parse the directives correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: { + 'strict-transport-security': 'max-age=31536000; includeSubDomains', + }, + ); + + final hsts = headers.strictTransportSecurity; + expect(hsts?.maxAge, equals(31536000)); + expect(hsts?.includeSubDomains, isTrue); + }, + ); + + test( + 'when a Strict-Transport-Security header without includeSubDomains is passed then it should parse correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.strictTransportSecurity, + headers: {'strict-transport-security': 'max-age=31536000'}, + ); + + final hsts = headers.strictTransportSecurity; + expect(hsts?.maxAge, equals(31536000)); + expect(hsts?.includeSubDomains, isFalse); + }, + ); + + test( + 'when a Strict-Transport-Security header with preload is passed then it should parse correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.strictTransportSecurity, + headers: {'strict-transport-security': 'max-age=31536000; preload'}, + ); + + final hsts = headers.strictTransportSecurity; + expect(hsts?.maxAge, equals(31536000)); + expect(hsts?.preload, isTrue); + }, + ); + + test( + 'when no Strict-Transport-Security header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.strictTransportSecurity, + headers: {}, + ); + + expect(headers.strictTransportSecurity, isNull); + }, + ); + }); group('Given a Strict-Transport-Security header without validation', () { late RelicServer server; @@ -164,20 +167,19 @@ void main() { tearDown(() => server.close()); group('when an empty Strict-Transport-Security header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'strict-transport-security': ''}, - ); - - expect(Headers.strictTransportSecurity[headers].valueOrNullIfInvalid, - isNull); - expect(() => headers.strictTransportSecurity, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'strict-transport-security': ''}, + ); + + expect( + Headers.strictTransportSecurity[headers].valueOrNullIfInvalid, + isNull, + ); + expect(() => headers.strictTransportSecurity, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/te_header_test.dart b/test/headers/typed/te_header_test.dart index 6cc345e5..b891125e 100644 --- a/test/headers/typed/te_header_test.dart +++ b/test/headers/typed/te_header_test.dart @@ -16,150 +16,126 @@ void main() { tearDown(() => server.close()); - test( - 'when an empty TE header is passed then the server responds ' - 'with a bad request including a message that states the header value ' - 'cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.te, - headers: {'te': ''}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - ), - ), - ); - }, - ); - - test( - 'when a TE header with invalid quality values is passed ' - 'then the server responds with a bad request including a message that ' - 'states the quality value is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.te, - headers: {'te': 'trailers;q=abc'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid quality value'), - ), - ), - ); - }, - ); - - test( - 'when a TE header with invalid encoding is passed ' - 'then the server responds with a bad request including a message that ' - 'states the encoding is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.te, - headers: {'te': ';q=1.0'}, - ), - throwsA( - isA().having( - (final e) => e.message, - 'message', - contains('Invalid encoding'), - ), + test('when an empty TE header is passed then the server responds ' + 'with a bad request including a message that states the header value ' + 'cannot be empty', () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.te, + headers: {'te': ''}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), ), - ); - }, - ); - - test( - 'when a TE header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( + ), + ); + }); + + test('when a TE header with invalid quality values is passed ' + 'then the server responds with a bad request including a message that ' + 'states the quality value is invalid', () async { + expect( + getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (final h) => h.te, headers: {'te': 'trailers;q=abc'}, - ); - - expect(headers, isNotNull); - }, - ); + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid quality value'), + ), + ), + ); + }); - test( - 'when a TE header is passed then it should parse the ' - 'encoding correctly', - () async { - final headers = await getServerRequestHeaders( + test('when a TE header with invalid encoding is passed ' + 'then the server responds with a bad request including a message that ' + 'states the encoding is invalid', () async { + expect( + getServerRequestHeaders( server: server, touchHeaders: (final h) => h.te, - headers: {'te': 'trailers'}, - ); + headers: {'te': ';q=1.0'}, + ), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid encoding'), + ), + ), + ); + }); - expect( - headers.te?.encodings.map((final e) => e.encoding).toList(), - equals(['trailers']), - ); - }, - ); + test('when a TE header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'te': 'trailers;q=abc'}, + ); - test( - 'when a TE header is passed without quality then the ' - 'default quality value should be set', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.te, - headers: {'te': 'trailers'}, - ); + expect(headers, isNotNull); + }); - expect( - headers.te?.encodings.map((final e) => e.quality).toList(), - equals([1.0]), - ); - }, - ); + test('when a TE header is passed then it should parse the ' + 'encoding correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.te, + headers: {'te': 'trailers'}, + ); + + expect( + headers.te?.encodings.map((final e) => e.encoding).toList(), + equals(['trailers']), + ); + }); - test( - 'when no TE header is passed then it should return null', - () async { + test('when a TE header is passed without quality then the ' + 'default quality value should be set', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.te, + headers: {'te': 'trailers'}, + ); + + expect( + headers.te?.encodings.map((final e) => e.quality).toList(), + equals([1.0]), + ); + }); + + test('when no TE header is passed then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.te, + headers: {}, + ); + + expect(headers.te, isNull); + }); + + group('when multiple TE headers are passed', () { + test('then they should parse the encodings correctly', () async { final headers = await getServerRequestHeaders( server: server, touchHeaders: (final h) => h.te, - headers: {}, + headers: {'te': 'trailers, deflate, gzip'}, ); - expect(headers.te, isNull); - }, - ); - - group('when multiple TE headers are passed', () { - test( - 'then they should parse the encodings correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.te, - headers: {'te': 'trailers, deflate, gzip'}, - ); - - expect( - headers.te?.encodings.map((final e) => e.encoding).toList(), - equals(['trailers', 'deflate', 'gzip']), - ); - }, - ); + expect( + headers.te?.encodings.map((final e) => e.encoding).toList(), + equals(['trailers', 'deflate', 'gzip']), + ); + }); test( 'with quantities then they should parse the encodings correctly', @@ -221,35 +197,29 @@ void main() { tearDown(() => server.close()); group('when an empty TE header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'te': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'te': ''}, + ); - expect(Headers.te[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.te, throwsInvalidHeader); - }, - ); + expect(Headers.te[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.te, throwsInvalidHeader); + }); }); group('when TE headers with invalid quality values are passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'te': 'trailers;q=abc, deflate, gzip'}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'te': 'trailers;q=abc, deflate, gzip'}, + ); - expect(Headers.te[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.te, throwsInvalidHeader); - }, - ); + expect(Headers.te[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.te, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/transfer_encoding_header_test.dart b/test/headers/typed/transfer_encoding_header_test.dart index a9c45b92..94db40e4 100644 --- a/test/headers/typed/transfer_encoding_header_test.dart +++ b/test/headers/typed/transfer_encoding_header_test.dart @@ -7,154 +7,153 @@ import '../headers_test_utils.dart'; /// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding /// For more details on header validation behavior, see the [HeaderValidationDocs] class. void main() { - group( - 'Given a Transfer-Encoding header with validation', - () { - late RelicServer server; + group('Given a Transfer-Encoding header with validation', () { + late RelicServer server; - setUp(() async { - server = await createServer(); - }); + setUp(() async { + server = await createServer(); + }); - tearDown(() => server.close()); - - test( - 'when an empty Transfer-Encoding header is passed then the server should respond with a bad request ' - 'including a message that states the encodings cannot be empty', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.transferEncoding, - headers: {'transfer-encoding': ''}, - ), - throwsA(isA().having( + tearDown(() => server.close()); + + test( + 'when an empty Transfer-Encoding header is passed then the server should respond with a bad request ' + 'including a message that states the encodings cannot be empty', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.transferEncoding, + headers: {'transfer-encoding': ''}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Value cannot be empty'), - )), - ); - }, - ); - - test( - 'when an invalid Transfer-Encoding header is passed then the server should respond with a bad request ' - 'including a message that states the value is invalid', - () async { - expect( - getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.transferEncoding, - headers: {'transfer-encoding': 'custom-encoding'}, ), - throwsA(isA().having( + ), + ); + }, + ); + + test( + 'when an invalid Transfer-Encoding header is passed then the server should respond with a bad request ' + 'including a message that states the value is invalid', + () async { + expect( + getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.transferEncoding, + headers: {'transfer-encoding': 'custom-encoding'}, + ), + throwsA( + isA().having( (final e) => e.message, 'message', contains('Invalid value'), - )), - ); - }, - ); + ), + ), + ); + }, + ); - test( - 'when a Transfer-Encoding header with an invalid value is passed ' + test('when a Transfer-Encoding header with an invalid value is passed ' 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'transfer-encoding': 'custom-encoding'}, - ); - - expect(headers, isNotNull); - }, + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'transfer-encoding': 'custom-encoding'}, ); - test( - 'when a valid Transfer-Encoding header is passed then it should parse the encodings correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.transferEncoding, - headers: {'transfer-encoding': 'gzip, chunked'}, - ); - - expect( - headers.transferEncoding?.encodings.map((final e) => e.name), - equals(['gzip', 'chunked']), - ); - }, - ); - - /// According to the HTTP/1.1 specification (RFC 9112), the 'chunked' transfer - /// encoding must be the final encoding applied to the response body. - test( - 'when a valid Transfer-Encoding header is passed with "chunked" as not the last ' - 'encoding then it should parse the encodings correctly and reorder them sot the ' - 'chunked encoding is the last encoding', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.transferEncoding, - headers: {'transfer-encoding': 'chunked, gzip'}, - ); - - expect( - headers.transferEncoding?.encodings.map((final e) => e.name), - equals(['gzip', 'chunked']), - ); - }, - ); - - test( - 'when a Transfer-Encoding header with duplicate encodings is passed then ' - 'it should parse the encodings correctly and remove duplicates', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.transferEncoding, - headers: {'transfer-encoding': 'gzip, chunked, chunked'}, - ); - - expect( - headers.transferEncoding?.encodings.map((final e) => e.name), - equals(['gzip', 'chunked']), - ); - }, - ); - - test( - 'when a Transfer-Encoding header contains "chunked" then isChunked should be true', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.transferEncoding, - headers: {'transfer-encoding': 'gzip, chunked'}, - ); - - expect( - headers.transferEncoding?.encodings - .any((final e) => e.name == TransferEncoding.chunked.name), - isTrue, - ); - }, - ); - - test( - 'when no Transfer-Encoding header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.transferEncoding, - headers: {}, - ); + expect(headers, isNotNull); + }); - expect(headers.transferEncoding, isNull); - }, - ); - }, - ); + test( + 'when a valid Transfer-Encoding header is passed then it should parse the encodings correctly', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.transferEncoding, + headers: {'transfer-encoding': 'gzip, chunked'}, + ); + + expect( + headers.transferEncoding?.encodings.map((final e) => e.name), + equals(['gzip', 'chunked']), + ); + }, + ); + + /// According to the HTTP/1.1 specification (RFC 9112), the 'chunked' transfer + /// encoding must be the final encoding applied to the response body. + test( + 'when a valid Transfer-Encoding header is passed with "chunked" as not the last ' + 'encoding then it should parse the encodings correctly and reorder them sot the ' + 'chunked encoding is the last encoding', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.transferEncoding, + headers: {'transfer-encoding': 'chunked, gzip'}, + ); + + expect( + headers.transferEncoding?.encodings.map((final e) => e.name), + equals(['gzip', 'chunked']), + ); + }, + ); + + test( + 'when a Transfer-Encoding header with duplicate encodings is passed then ' + 'it should parse the encodings correctly and remove duplicates', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.transferEncoding, + headers: {'transfer-encoding': 'gzip, chunked, chunked'}, + ); + + expect( + headers.transferEncoding?.encodings.map((final e) => e.name), + equals(['gzip', 'chunked']), + ); + }, + ); + + test( + 'when a Transfer-Encoding header contains "chunked" then isChunked should be true', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.transferEncoding, + headers: {'transfer-encoding': 'gzip, chunked'}, + ); + + expect( + headers.transferEncoding?.encodings.any( + (final e) => e.name == TransferEncoding.chunked.name, + ), + isTrue, + ); + }, + ); + + test( + 'when no Transfer-Encoding header is passed then it should return null', + () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.transferEncoding, + headers: {}, + ); + + expect(headers.transferEncoding, isNull); + }, + ); + }); group('Given a Transfer-Encoding header without validation', () { late RelicServer server; @@ -166,20 +165,16 @@ void main() { tearDown(() => server.close()); group('when an empty Transfer-Encoding header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'transfer-encoding': ''}, - ); - - expect( - Headers.transferEncoding[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.transferEncoding, throwsInvalidHeader); - }, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'transfer-encoding': ''}, + ); + + expect(Headers.transferEncoding[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.transferEncoding, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/upgrade_header_test.dart b/test/headers/typed/upgrade_header_test.dart index 3e1ecba4..a59e8464 100644 --- a/test/headers/typed/upgrade_header_test.dart +++ b/test/headers/typed/upgrade_header_test.dart @@ -26,11 +26,13 @@ void main() { touchHeaders: (final h) => h.upgrade, headers: {'upgrade': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -46,36 +48,35 @@ void main() { touchHeaders: (final h) => h.upgrade, headers: {'upgrade': 'InvalidProtocol/abc'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid version'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid version'), + ), + ), ); }, ); - test( - 'when a Upgrade header with an invalid protocol is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'upgrade': 'InvalidProtocol/abc'}, - ); + test('when a Upgrade header with an invalid protocol is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'upgrade': 'InvalidProtocol/abc'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when no Upgrade header is passed then it should return null', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {}, ); @@ -94,32 +95,31 @@ void main() { touchHeaders: (final h) => h.upgrade, headers: {'upgrade': 'HTTP/2.0, HTTP/abc'}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Invalid version'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Invalid version'), + ), + ), ); }, ); - test( - 'then it should parse the protocols correctly', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.upgrade, - headers: {'upgrade': 'HTTP/2.0, WebSocket'}, - ); + test('then it should parse the protocols correctly', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.upgrade, + headers: {'upgrade': 'HTTP/2.0, WebSocket'}, + ); - final protocols = headers.upgrade?.protocols; - expect(protocols?.length, equals(2)); - expect(protocols?[0].protocol, equals('HTTP')); - expect(protocols?[0].version, equals(2)); - expect(protocols?[1].protocol, equals('WebSocket')); - expect(protocols?[1].version, isNull); - }, - ); + final protocols = headers.upgrade?.protocols; + expect(protocols?.length, equals(2)); + expect(protocols?[0].protocol, equals('HTTP')); + expect(protocols?[0].version, equals(2)); + expect(protocols?[1].protocol, equals('WebSocket')); + expect(protocols?[1].version, isNull); + }); test( 'with duplicate protocols then it should parse the protocols correctly ' diff --git a/test/headers/typed/vary_header_test.dart b/test/headers/typed/vary_header_test.dart index 60ced551..87d0d693 100644 --- a/test/headers/typed/vary_header_test.dart +++ b/test/headers/typed/vary_header_test.dart @@ -26,11 +26,13 @@ void main() { touchHeaders: (final h) => h.vary, headers: {'vary': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); @@ -57,20 +59,17 @@ void main() { }, ); - test( - 'when a Vary header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'vary': '* , User-Agent'}, - ); + test('when a Vary header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'vary': '* , User-Agent'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); test( 'when a Vary header is passed then it should parse correctly', @@ -81,10 +80,7 @@ void main() { headers: {'vary': 'Accept-Encoding, User-Agent'}, ); - expect( - headers.vary?.fields, - equals(['Accept-Encoding', 'User-Agent']), - ); + expect(headers.vary?.fields, equals(['Accept-Encoding', 'User-Agent'])); }, ); @@ -97,10 +93,7 @@ void main() { headers: {'vary': ' Accept-Encoding , User-Agent '}, ); - expect( - headers.vary?.fields, - equals(['Accept-Encoding', 'User-Agent']), - ); + expect(headers.vary?.fields, equals(['Accept-Encoding', 'User-Agent'])); }, ); @@ -118,18 +111,15 @@ void main() { }, ); - test( - 'when no Vary header is passed then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final h) => h.vary, - headers: {}, - ); + test('when no Vary header is passed then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (final h) => h.vary, + headers: {}, + ); - expect(headers.vary, isNull); - }, - ); + expect(headers.vary, isNull); + }); }); group('Given a Vary header without validation', () { @@ -142,19 +132,16 @@ void main() { tearDown(() => server.close()); group('when an empty Vary header is passed', () { - test( - 'then it should return null', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'vary': ''}, - ); + test('then it should return null', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'vary': ''}, + ); - expect(Headers.vary[headers].valueOrNullIfInvalid, isNull); - expect(() => headers.vary, throwsInvalidHeader); - }, - ); + expect(Headers.vary[headers].valueOrNullIfInvalid, isNull); + expect(() => headers.vary, throwsInvalidHeader); + }); }); }); } diff --git a/test/headers/typed/www_authenticate_header_test.dart b/test/headers/typed/www_authenticate_header_test.dart index 65f495b5..d30046fc 100644 --- a/test/headers/typed/www_authenticate_header_test.dart +++ b/test/headers/typed/www_authenticate_header_test.dart @@ -25,29 +25,28 @@ void main() { touchHeaders: (final h) => h.wwwAuthenticate, headers: {'www-authenticate': ''}, ), - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('Value cannot be empty'), - )), + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('Value cannot be empty'), + ), + ), ); }, ); - test( - 'when a WWW-Authenticate header with an invalid value is passed ' - 'then the server does not respond with a bad request if the headers ' - 'is not actually used', - () async { - final headers = await getServerRequestHeaders( - server: server, - touchHeaders: (final _) {}, - headers: {'www-authenticate': 'Test'}, - ); + test('when a WWW-Authenticate header with an invalid value is passed ' + 'then the server does not respond with a bad request if the headers ' + 'is not actually used', () async { + final headers = await getServerRequestHeaders( + server: server, + touchHeaders: (_) {}, + headers: {'www-authenticate': 'Test'}, + ); - expect(headers, isNotNull); - }, - ); + expect(headers, isNotNull); + }); group('when Basic authentication', () { test('with realm parameter should parse scheme correctly', () async { @@ -80,10 +79,10 @@ void main() { test('should parse scheme correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'www-authenticate': - 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"' + 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"', }, ); @@ -93,10 +92,10 @@ void main() { test('should parse realm parameter correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'www-authenticate': - 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"' + 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"', }, ); @@ -111,10 +110,10 @@ void main() { test('should parse qop parameter correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'www-authenticate': - 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"' + 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"', }, ); @@ -129,10 +128,10 @@ void main() { test('should parse nonce parameter correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'www-authenticate': - 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"' + 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"', }, ); @@ -147,10 +146,10 @@ void main() { test('should parse opaque parameter correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'www-authenticate': - 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"' + 'Digest realm="Protected Area", qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"', }, ); @@ -177,10 +176,10 @@ void main() { test('with error parameter should parse error correctly', () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: { 'www-authenticate': - 'Bearer realm="Protected API", error="invalid_token"' + 'Bearer realm="Protected API", error="invalid_token"', }, ); @@ -221,7 +220,7 @@ void main() { () async { final headers = await getServerRequestHeaders( server: server, - touchHeaders: (final _) {}, + touchHeaders: (_) {}, headers: {'www-authenticate': 'InvalidHeader'}, ); diff --git a/test/headers/typed/x_forwarded_for_header_test.dart b/test/headers/typed/x_forwarded_for_header_test.dart index 08daf727..57ccdf9e 100644 --- a/test/headers/typed/x_forwarded_for_header_test.dart +++ b/test/headers/typed/x_forwarded_for_header_test.dart @@ -4,8 +4,7 @@ import 'package:test/test.dart'; void main() { group('XForwardedForHeader', () { group('parse', () { - test( - 'Given a single IP address, ' + test('Given a single IP address, ' 'when parsed, ' 'then addresses list contains that IP', () { final values = ['192.0.2.43']; @@ -16,15 +15,14 @@ void main() { expect(header.addresses, equals(expectedAddresses)); }); - test( - 'Given multiple IP addresses comma-separated, ' + test('Given multiple IP addresses comma-separated, ' 'when parsed, ' 'then addresses list contains all IPs in order', () { final values = ['192.0.2.43, 198.51.100.10, 203.0.113.60']; final expectedAddresses = [ '192.0.2.43', '198.51.100.10', - '203.0.113.60' + '203.0.113.60', ]; final header = XForwardedForHeader.parse(values); @@ -32,15 +30,14 @@ void main() { expect(header.addresses, orderedEquals(expectedAddresses)); }); - test( - 'Given multiple IP addresses with varying spacing, ' + test('Given multiple IP addresses with varying spacing, ' 'when parsed, ' 'then addresses are trimmed and correct', () { final values = ['192.0.2.43,198.51.100.10 , 203.0.113.60']; final expectedAddresses = [ '192.0.2.43', '198.51.100.10', - '203.0.113.60' + '203.0.113.60', ]; final header = XForwardedForHeader.parse(values); @@ -48,8 +45,7 @@ void main() { expect(header.addresses, orderedEquals(expectedAddresses)); }); - test( - 'Given IP addresses including "unknown", ' + test('Given IP addresses including "unknown", ' 'when parsed, ' 'then "unknown" is included as an address', () { final values = ['unknown, 192.0.2.43, unknown']; @@ -60,8 +56,7 @@ void main() { expect(header.addresses, orderedEquals(expectedAddresses)); }); - test( - 'Given multiple header lines, ' + test('Given multiple header lines, ' 'when parsed, ' 'then addresses from all lines are combined and split correctly', () { final values = ['192.0.2.43, 198.51.100.10', '203.0.113.60, 10.0.0.1']; @@ -69,7 +64,7 @@ void main() { '192.0.2.43', '198.51.100.10', '203.0.113.60', - '10.0.0.1' + '10.0.0.1', ]; final header = XForwardedForHeader.parse(values); @@ -77,28 +72,29 @@ void main() { expect(header.addresses, orderedEquals(expectedAddresses)); }); - test( - 'Given empty input values, ' + test('Given empty input values, ' 'when parsed, ' 'then it throws a FormatException', () { final values = []; - expect(() => XForwardedForHeader.parse(values), - throwsA(isA())); + expect( + () => XForwardedForHeader.parse(values), + throwsA(isA()), + ); }); - test( - 'Given input values with only commas or whitespace, ' + test('Given input values with only commas or whitespace, ' 'when parsed, ' 'then it throws a FormatException', () { final values = [', ,', ' ', ' , ']; - expect(() => XForwardedForHeader.parse(values), - throwsA(isA())); + expect( + () => XForwardedForHeader.parse(values), + throwsA(isA()), + ); }); - test( - 'Given input values that are empty strings, ' + test('Given input values that are empty strings, ' 'when parsed, ' 'then it throws a FormatException', () { // This case assumes that if X-Forwarded-For header is present @@ -106,32 +102,34 @@ void main() { // it's a malformed header. This may be controversial? final values = ['']; - expect(() => XForwardedForHeader.parse(values), - throwsA(isA())); + expect( + () => XForwardedForHeader.parse(values), + throwsA(isA()), + ); }); - test( - 'Given input values that are multiple empty strings, ' + test('Given input values that are multiple empty strings, ' 'when parsed, ' 'then it throws a FormatException', () { final values = [ '', - '' + '', ]; // Represents multiple X-Forwarded-For: headers that are empty - expect(() => XForwardedForHeader.parse(values), - throwsA(isA())); + expect( + () => XForwardedForHeader.parse(values), + throwsA(isA()), + ); }); - test( - 'Given IPv6 addresses and obfuscated identifiers, ' + test('Given IPv6 addresses and obfuscated identifiers, ' 'when parsed, ' 'then they are treated as simple strings', () { final values = ['[2001:db8::1], _hidden, 192.0.2.43:8080']; final expectedAddresses = [ '[2001:db8::1]', '_hidden', - '192.0.2.43:8080' + '192.0.2.43:8080', ]; // XFF doesn't parse ports, it's just a string final header = XForwardedForHeader.parse(values); @@ -141,36 +139,39 @@ void main() { }); group('Immutability', () { - test( - 'Given an XForwardedForHeader, ' + test('Given an XForwardedForHeader, ' 'when attempting to modify addresses list, ' 'then it should throw an UnsupportedError', () { final header = XForwardedForHeader(['192.0.2.43']); - expect(() => header.addresses.add('another-ip'), - throwsA(isA())); expect( - () => header.addresses.clear(), throwsA(isA())); + () => header.addresses.add('another-ip'), + throwsA(isA()), + ); + expect( + () => header.addresses.clear(), + throwsA(isA()), + ); }); }); group('Equality and hashCode', () { - test( - 'Given two XForwardedForHeader instances with the same addresses, ' + test('Given two XForwardedForHeader instances with the same addresses, ' 'when compared, ' 'then they are equal and have the same hashCode', () { final addresses = ['192.0.2.43', '198.51.100.10']; - final header1 = - XForwardedForHeader(List.of(addresses)); // Ensure new list - final header2 = - XForwardedForHeader(List.of(addresses)); // Ensure new list + final header1 = XForwardedForHeader( + List.of(addresses), + ); // Ensure new list + final header2 = XForwardedForHeader( + List.of(addresses), + ); // Ensure new list expect(header1, equals(header2)); expect(header1.hashCode, equals(header2.hashCode)); }); - test( - 'Given two XForwardedForHeader instances with different addresses, ' + test('Given two XForwardedForHeader instances with different addresses, ' 'when compared, ' 'then they are not equal', () { final header1 = XForwardedForHeader(['192.0.2.43', '198.51.100.10']); @@ -180,17 +181,18 @@ void main() { }); test( - 'Given two XForwardedForHeader instances with addresses in different order, ' - 'when compared, ' - 'then they are not equal', () { - final header1 = XForwardedForHeader(['192.0.2.43', '198.51.100.10']); - final header2 = XForwardedForHeader(['198.51.100.10', '192.0.2.43']); - - expect(header1, isNot(equals(header2))); - }); - - test( - 'Given an XForwardedForHeader and a different type, ' + 'Given two XForwardedForHeader instances with addresses in different order, ' + 'when compared, ' + 'then they are not equal', + () { + final header1 = XForwardedForHeader(['192.0.2.43', '198.51.100.10']); + final header2 = XForwardedForHeader(['198.51.100.10', '192.0.2.43']); + + expect(header1, isNot(equals(header2))); + }, + ); + + test('Given an XForwardedForHeader and a different type, ' 'when compared, ' 'then they are not equal', () { final header = XForwardedForHeader(['192.0.2.43']); @@ -202,12 +204,14 @@ void main() { }); group('codec', () { - test( - 'Given an XForwardedForHeader, ' + test('Given an XForwardedForHeader, ' 'when encoded using codec, ' 'then it produces the correct string list', () { - final header = - XForwardedForHeader(['192.0.2.43', '198.51.100.10', 'unknown']); + final header = XForwardedForHeader([ + '192.0.2.43', + '198.51.100.10', + 'unknown', + ]); final expectedStrings = ['192.0.2.43, 198.51.100.10, unknown']; final encoded = XForwardedForHeader.codec.encode(header); @@ -215,38 +219,50 @@ void main() { expect(encoded, orderedEquals(expectedStrings)); }); - test( - 'Given a list of strings, ' + test('Given a list of strings, ' 'when parsed using codec, ' 'then it produces the correct XForwardedForHeader', () { final values = ['192.0.2.43, 198.51.100.10', 'unknown, 10.0.0.1']; - final expectedHeader = XForwardedForHeader( - ['192.0.2.43', '198.51.100.10', 'unknown', '10.0.0.1']); + final expectedHeader = XForwardedForHeader([ + '192.0.2.43', + '198.51.100.10', + 'unknown', + '10.0.0.1', + ]); final parsed = XForwardedForHeader.codec.decode(values); expect(parsed, equals(expectedHeader)); }); - test( - 'Given an empty list of strings, ' + test('Given an empty list of strings, ' 'when parsed using codec, ' 'then it throws a FormatException', () { final values = []; - expect(() => XForwardedForHeader.codec.decode(values), - throwsA(isA())); + expect( + () => XForwardedForHeader.codec.decode(values), + throwsA(isA()), + ); }); test( - 'Given a list of strings that result in no valid addresses after parsing, ' - 'when parsed using codec, ' - 'then it throws a FormatException', () { - final values = [', ', ' ', ',,']; // Values that would be filtered out - - expect(() => XForwardedForHeader.codec.decode(values), - throwsA(isA())); - }); + 'Given a list of strings that result in no valid addresses after parsing, ' + 'when parsed using codec, ' + 'then it throws a FormatException', + () { + final values = [ + ', ', + ' ', + ',,', + ]; // Values that would be filtered out + + expect( + () => XForwardedForHeader.codec.decode(values), + throwsA(isA()), + ); + }, + ); }); }); } diff --git a/test/hijack/relic_hijack_test.dart b/test/hijack/relic_hijack_test.dart index 7a081f40..18b5d207 100644 --- a/test/hijack/relic_hijack_test.dart +++ b/test/hijack/relic_hijack_test.dart @@ -22,24 +22,16 @@ void main() { }); group('Given a server', () { - test( - 'when request context is hijacked ' - 'then an HijackContext is returned and the request times out because ' - 'server does not write the response to the HTTP response', - () async { - await _scheduleServer( - (final ctx) { - final newCtx = ctx.hijack((final _) {}); - expect(newCtx, isA()); - return newCtx; - }, - ); - expect( - _get(), - throwsA(isA()), - ); - }, - ); + test('when request context is hijacked ' + 'then an HijackContext is returned and the request times out because ' + 'server does not write the response to the HTTP response', () async { + await _scheduleServer((final ctx) { + final newCtx = ctx.hijack((_) {}); + expect(newCtx, isA()); + return newCtx; + }); + expect(_get(), throwsA(isA())); + }); }); } @@ -62,6 +54,7 @@ Future _get({ if (headers != null) request.headers.addAll(headers); final response = await request.send().timeout(const Duration(seconds: 1)); - return await http.Response.fromStream(response) - .timeout(const Duration(seconds: 1)); + return await http.Response.fromStream( + response, + ).timeout(const Duration(seconds: 1)); } diff --git a/test/isolated_object/isolated_object_close_test.dart b/test/isolated_object/isolated_object_close_test.dart index e2582c7d..4d4c422a 100644 --- a/test/isolated_object/isolated_object_close_test.dart +++ b/test/isolated_object/isolated_object_close_test.dart @@ -4,8 +4,7 @@ import 'package:relic/src/isolated_object.dart'; import 'package:test/test.dart'; void main() { - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when close is called multiple times, ' 'then it handles it gracefully', () async { final isolated = IsolatedObject<_Counter>(() => _Counter(0)); @@ -17,8 +16,7 @@ void main() { expect(true, isTrue); }); - test( - 'Given an IsolatedObject with pending operations, ' + test('Given an IsolatedObject with pending operations, ' 'when the channel is closed, ' 'then pending operations fail with channel closed error', () async { final isolated = IsolatedObject<_Counter>(() => _Counter(0)); diff --git a/test/isolated_object/isolated_object_create_test.dart b/test/isolated_object/isolated_object_create_test.dart index 3e624c9e..bd02c63a 100644 --- a/test/isolated_object/isolated_object_create_test.dart +++ b/test/isolated_object/isolated_object_create_test.dart @@ -4,8 +4,7 @@ import 'package:relic/src/isolated_object.dart'; import 'package:test/test.dart'; void main() { - test( - 'Given a synchronous factory, ' + test('Given a synchronous factory, ' 'when IsolatedObject is created, ' 'then it successfully initializes', () async { final isolated = IsolatedObject<_Counter>(() => _Counter(0)); @@ -17,8 +16,7 @@ void main() { await isolated.close(); }); - test( - 'Given an async factory, ' + test('Given an async factory, ' 'when IsolatedObject is created, ' 'then it successfully initializes', () async { final isolated = IsolatedObject<_Counter>(() async { @@ -32,8 +30,7 @@ void main() { await isolated.close(); }); - test( - 'Given a factory that throws, ' + test('Given a factory that throws, ' 'when IsolatedObject is created, ' 'then subsequent operations fail gracefully', () async { final isolated = IsolatedObject<_Counter>(() { diff --git a/test/isolated_object/isolated_object_evaluate_test.dart b/test/isolated_object/isolated_object_evaluate_test.dart index e685ea83..3b06bc47 100644 --- a/test/isolated_object/isolated_object_evaluate_test.dart +++ b/test/isolated_object/isolated_object_evaluate_test.dart @@ -16,16 +16,14 @@ void main() { await isolated.close(); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when evaluate is called with a getter, ' 'then it returns the correct value', () async { final result = await isolated.evaluate((final counter) => counter.value); expect(result, 0); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when evaluate is called with a mutation, ' 'then the state persists across evaluations', () async { await isolated.evaluate((final counter) { @@ -37,8 +35,7 @@ void main() { expect(result, 1); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when evaluate is called multiple times sequentially, ' 'then all operations execute correctly', () async { await isolated.evaluate((final counter) => counter.increment()); @@ -49,8 +46,7 @@ void main() { expect(result, 3); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when evaluate is called with multiple concurrent operations, ' 'then all operations complete successfully', () async { final futures = List.generate( @@ -64,28 +60,29 @@ void main() { expect(result, 10); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when evaluate is called with different return types, ' 'then it returns the correct types', () async { - final intResult = - await isolated.evaluate((final counter) => counter.value); + final intResult = await isolated.evaluate( + (final counter) => counter.value, + ); expect(intResult, isA()); expect(intResult, 0); - final stringResult = await isolated - .evaluate((final counter) => 'Value: ${counter.value}'); + final stringResult = await isolated.evaluate( + (final counter) => 'Value: ${counter.value}', + ); expect(stringResult, isA()); expect(stringResult, 'Value: 0'); - final boolResult = - await isolated.evaluate((final counter) => counter.value > 0); + final boolResult = await isolated.evaluate( + (final counter) => counter.value > 0, + ); expect(boolResult, isA()); expect(boolResult, false); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when evaluate is called with async function, ' 'then it awaits the result correctly', () async { await isolated.evaluate((final counter) async { @@ -97,8 +94,7 @@ void main() { expect(result, 1); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when evaluate is called with a function returning complex objects, ' 'then it returns the correct data', () async { final result = await isolated.evaluate((final counter) { @@ -115,8 +111,7 @@ void main() { expect(result['info'], 'Counter state'); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when evaluate is called with a void function, ' 'then it executes the function without returning a value', () async { await isolated.evaluate((final counter) => counter.increment()); @@ -125,8 +120,7 @@ void main() { expect(result, 1); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when evaluate is called with a void function that throws, ' 'then it propagates the error', () async { expect( @@ -139,8 +133,7 @@ void main() { }); group('error handling', () { - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when a function throws in the isolate, ' 'then RemoteError contains error information', () async { final isolated = IsolatedObject<_Counter>(() => _Counter(0)); @@ -158,8 +151,7 @@ void main() { } }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when an error occurs, ' 'then subsequent operations still work', () async { final isolated = IsolatedObject<_Counter>(() => _Counter(5)); @@ -182,8 +174,7 @@ void main() { }); group('concurrent operations', () { - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when many operations are queued concurrently, ' 'then all complete with unique IDs', () async { final isolated = IsolatedObject<_Counter>(() => _Counter(0)); @@ -191,10 +182,12 @@ void main() { // Queue many operations concurrently final futures = >[]; for (var i = 0; i < 100; i++) { - futures.add(isolated.evaluate((final counter) { - counter.increment(); - return counter.value; - })); + futures.add( + isolated.evaluate((final counter) { + counter.increment(); + return counter.value; + }), + ); } final results = await Future.wait(futures); @@ -203,15 +196,15 @@ void main() { expect(results.length, 100); // Final value should be 100 - final finalValue = - await isolated.evaluate((final counter) => counter.value); + final finalValue = await isolated.evaluate( + (final counter) => counter.value, + ); expect(finalValue, 100); await isolated.close(); }); - test( - 'Given an IsolatedObject, ' + test('Given an IsolatedObject, ' 'when concurrent operations include some that throw, ' 'then successful operations still complete', () async { final isolated = IsolatedObject<_Counter>(() => _Counter(0)); @@ -221,9 +214,10 @@ void main() { try { await [ for (var i = 0; i < ops; i++) - i % 2 == 0 // even succeeds, odd fails + i % 2 == + 0 // even succeeds, odd fails ? isolated.evaluate((final counter) => counter.increment()) - : isolated.evaluate((final _) => throw Exception('Error $i')) + : isolated.evaluate((_) => throw Exception('Error $i')), ].wait; } on ParallelWaitError> catch (e) { expect(e.errors.nonNulls.length, ops / 2); // half fails @@ -238,8 +232,7 @@ void main() { }); group('complex objects', () { - test( - 'Given an IsolatedObject with a complex state object, ' + test('Given an IsolatedObject with a complex state object, ' 'when operations modify nested state, ' 'then changes persist correctly', () async { final isolated = IsolatedObject<_ComplexObject>(() => _ComplexObject()); @@ -255,8 +248,7 @@ void main() { await isolated.close(); }); - test( - 'Given an IsolatedObject with state, ' + test('Given an IsolatedObject with state, ' 'when evaluating functions that return lists, ' 'then lists are correctly transferred', () async { final isolated = IsolatedObject<_ComplexObject>(() => _ComplexObject()); diff --git a/test/message/apply_headers_test.dart b/test/message/apply_headers_test.dart index b7d8b3bd..557c87d3 100644 --- a/test/message/apply_headers_test.dart +++ b/test/message/apply_headers_test.dart @@ -41,9 +41,7 @@ class HttpResponseMock extends Mock implements HttpResponse { final HttpHeadersMock _headers = HttpHeadersMock(); final int _statusCode; - HttpResponseMock({ - final int statusCode = 200, - }) : _statusCode = statusCode; + HttpResponseMock({final int statusCode = 200}) : _statusCode = statusCode; @override HttpHeaders get headers => _headers; @@ -54,8 +52,7 @@ class HttpResponseMock extends Mock implements HttpResponse { void main() { group('Given a framework response object', () { - test( - 'with some headers ' + test('with some headers ' 'when applying headers to http response ' 'then the headers are applied correctly', () async { final HttpResponseMock response = HttpResponseMock(); @@ -77,25 +74,22 @@ void main() { expect(response.headers['date'], equals([formatHttpDate(now)])); expect(response.headers['expires'], equals([formatHttpDate(expires)])); - expect(response.headers['last-modified'], - equals([formatHttpDate(lastModified)])); + expect( + response.headers['last-modified'], + equals([formatHttpDate(lastModified)]), + ); expect(response.headers['origin'], equals(['https://example.com'])); expect(response.headers['server'], equals(['Relic'])); expect(response.headers['foo'], equals(['bar'])); }); - test( - 'with mime type and encoding ' + test('with mime type and encoding ' 'when applying headers to http response ' 'then the content type is applied correctly', () async { final HttpResponseMock response = HttpResponseMock(); response.applyHeaders( Headers.empty(), - Body.fromString( - 'Relic', - mimeType: MimeType.plainText, - encoding: utf8, - ), + Body.fromString('Relic', mimeType: MimeType.plainText, encoding: utf8), ); expect( @@ -104,17 +98,14 @@ void main() { ); }); - test( - 'with a known content length ' + test('with a known content length ' 'when applying headers to http response ' 'then the content length is applied correctly', () async { final HttpResponseMock response = HttpResponseMock(); response.applyHeaders( Headers.empty(), Body.fromDataStream( - Stream.fromIterable([ - Uint8List.fromList('Relic'.codeUnits), - ]), + Stream.fromIterable([Uint8List.fromList('Relic'.codeUnits)]), contentLength: 5, ), ); @@ -122,16 +113,13 @@ void main() { expect(response.headers.contentLength, equals(5)); }); - test( - 'with an unknown content length ' + test('with an unknown content length ' 'when applying headers to http response ' 'then chunked transfer encoding is added', () async { final HttpResponseMock response = HttpResponseMock(statusCode: 200); response.applyHeaders( Headers.empty(), - Body.fromDataStream( - const Stream.empty(), - ), + Body.fromDataStream(const Stream.empty()), ); expect( @@ -140,20 +128,18 @@ void main() { ); }); - test( - 'with "identity" transfer encoding and unknown content length ' + test('with "identity" transfer encoding and unknown content length ' 'when applying headers to http response ' 'then no "chunked" transfer encoding is added', () async { final HttpResponseMock response = HttpResponseMock(); response.applyHeaders( Headers.build( - (final mh) => mh.transferEncoding = TransferEncodingHeader( - encodings: [TransferEncoding.identity], - ), - ), - Body.fromDataStream( - const Stream.empty(), + (final mh) => + mh.transferEncoding = TransferEncodingHeader( + encodings: [TransferEncoding.identity], + ), ), + Body.fromDataStream(const Stream.empty()), ); expect( @@ -162,16 +148,16 @@ void main() { ); }); - test( - 'with "chunked" transfer encoding already applied ' + test('with "chunked" transfer encoding already applied ' 'when applying headers to http response ' 'then "chunked" is retained', () async { final HttpResponseMock response = HttpResponseMock(); response.applyHeaders( Headers.build( - (final mh) => mh.transferEncoding = TransferEncodingHeader( - encodings: [TransferEncoding.chunked], - ), + (final mh) => + mh.transferEncoding = TransferEncodingHeader( + encodings: [TransferEncoding.chunked], + ), ), Body.fromDataStream( Stream.fromIterable([ @@ -187,16 +173,13 @@ void main() { }); group('with status code', () { - test( - '100 (continue) ' + test('100 (continue) ' 'when applying headers to http response ' 'then no chunked transfer encoding is added', () async { final HttpResponseMock response = HttpResponseMock(statusCode: 100); response.applyHeaders( Headers.empty(), - Body.fromDataStream( - const Stream.empty(), - ), + Body.fromDataStream(const Stream.empty()), ); expect( @@ -205,16 +188,13 @@ void main() { ); }); - test( - '101 (switching protocols) ' + test('101 (switching protocols) ' 'when applying headers to http response ' 'then no chunked transfer encoding is added', () async { final HttpResponseMock response = HttpResponseMock(statusCode: 101); response.applyHeaders( Headers.empty(), - Body.fromDataStream( - const Stream.empty(), - ), + Body.fromDataStream(const Stream.empty()), ); expect( @@ -223,16 +203,13 @@ void main() { ); }); - test( - '102 (processing) ' + test('102 (processing) ' 'when applying headers to http response ' 'then no chunked transfer encoding is added', () async { final HttpResponseMock response = HttpResponseMock(statusCode: 102); response.applyHeaders( Headers.empty(), - Body.fromDataStream( - const Stream.empty(), - ), + Body.fromDataStream(const Stream.empty()), ); expect( @@ -241,16 +218,13 @@ void main() { ); }); - test( - '103 (early hints) ' + test('103 (early hints) ' 'when applying headers to http response ' 'then no chunked transfer encoding is added', () async { final HttpResponseMock response = HttpResponseMock(statusCode: 103); response.applyHeaders( Headers.empty(), - Body.fromDataStream( - const Stream.empty(), - ), + Body.fromDataStream(const Stream.empty()), ); expect( @@ -259,16 +233,13 @@ void main() { ); }); - test( - '204 (no content) ' + test('204 (no content) ' 'when applying headers to http response ' 'then no chunked transfer encoding is added', () async { final HttpResponseMock response = HttpResponseMock(statusCode: 204); response.applyHeaders( Headers.empty(), - Body.fromDataStream( - const Stream.empty(), - ), + Body.fromDataStream(const Stream.empty()), ); expect( @@ -277,16 +248,13 @@ void main() { ); }); - test( - '304 (not modified) ' + test('304 (not modified) ' 'when applying headers to http response ' 'then no chunked transfer encoding is added', () async { final HttpResponseMock response = HttpResponseMock(statusCode: 304); response.applyHeaders( Headers.empty(), - Body.fromDataStream( - const Stream.empty(), - ), + Body.fromDataStream(const Stream.empty()), ); expect( @@ -296,8 +264,7 @@ void main() { }); }); - test( - 'with mime type multipart/byteranges ' + test('with mime type multipart/byteranges ' 'when applying headers to http response ' 'then no chunked transfer encoding is added', () async { final HttpResponseMock response = HttpResponseMock(); diff --git a/test/message/message_test.dart b/test/message/message_test.dart index 87d86c33..184b99f3 100644 --- a/test/message/message_test.dart +++ b/test/message/message_test.dart @@ -13,10 +13,7 @@ class _TestMessage extends Message { final Headers? headers, final Map? context, { final Body? body, - }) : super( - headers: headers ?? Headers.empty(), - body: body ?? Body.empty(), - ); + }) : super(headers: headers ?? Headers.empty(), body: body ?? Body.empty()); @override Message copyWith({ @@ -52,18 +49,9 @@ void main() { final message = _createMessage( headers: Headers.build((final mh) => mh['foo'] = ['bar']), ); - expect( - () => message.headers['h1'] = ['value1'], - throwsUnsupportedError, - ); - expect( - () => message.headers['h1'] = ['value2'], - throwsUnsupportedError, - ); - expect( - () => message.headers['h2'] = ['value2'], - throwsUnsupportedError, - ); + expect(() => message.headers['h1'] = ['value1'], throwsUnsupportedError); + expect(() => message.headers['h1'] = ['value2'], throwsUnsupportedError); + expect(() => message.headers['h2'] = ['value2'], throwsUnsupportedError); }); test('when containing multiple values then they are handled correctly', () { @@ -89,8 +77,9 @@ void main() { test('when the body is a Stream then it reads correctly', () { final controller = StreamController(); - final request = - _createMessage(body: Body.fromDataStream(controller.stream)); + final request = _createMessage( + body: Body.fromDataStream(controller.stream), + ); expect(request.readAsString(), completion(equals('hello, world'))); controller.add(helloBytes); @@ -120,8 +109,10 @@ void main() { final request = _createMessage( body: Body.fromDataStream(controller.stream), ); - expect(request.read().toList(), - completion(equals([helloBytes, worldBytes]))); + expect( + request.read().toList(), + completion(equals([helloBytes, worldBytes])), + ); controller.add(helloBytes); return Future(() { @@ -137,35 +128,37 @@ void main() { }); test( - 'when read()/readAsString() is called multiple times then it throws a StateError', - () { - Message request; - - request = _createMessage(); - expect(request.read().toList(), completion(isEmpty)); - expect(() => request.read(), throwsStateError); - - request = _createMessage(); - expect(request.readAsString(), completion(isEmpty)); - expect(() => request.readAsString(), throwsStateError); - - request = _createMessage(); - expect(request.readAsString(), completion(isEmpty)); - expect(() => request.read(), throwsStateError); - - request = _createMessage(); - expect(request.read().toList(), completion(isEmpty)); - expect(() => request.readAsString(), throwsStateError); - }); + 'when read()/readAsString() is called multiple times then it throws a StateError', + () { + Message request; + + request = _createMessage(); + expect(request.read().toList(), completion(isEmpty)); + expect(() => request.read(), throwsStateError); + + request = _createMessage(); + expect(request.readAsString(), completion(isEmpty)); + expect(() => request.readAsString(), throwsStateError); + + request = _createMessage(); + expect(request.readAsString(), completion(isEmpty)); + expect(() => request.read(), throwsStateError); + + request = _createMessage(); + expect(request.read().toList(), completion(isEmpty)); + expect(() => request.readAsString(), throwsStateError); + }, + ); }); group('Given content-length', () { test( - 'when the body is default and no content-length header is present then it is 0', - () { - final request = _createMessage(); - expect(request.body.contentLength, 0); - }); + 'when the body is default and no content-length header is present then it is 0', + () { + final request = _createMessage(); + expect(request.body.contentLength, 0); + }, + ); test('when the body is a byte body then it is set correctly', () { final request = _createMessage( @@ -200,9 +193,10 @@ void main() { final request = _createMessage( body: Body.fromString('1\r\na0\r\n\r\n'), headers: Headers.build( - (final mh) => mh.transferEncoding = TransferEncodingHeader( - encodings: [TransferEncoding.identity], - ), + (final mh) => + mh.transferEncoding = TransferEncodingHeader( + encodings: [TransferEncoding.identity], + ), ), ); expect(request.body.contentLength, equals(9)); @@ -211,21 +205,17 @@ void main() { group('Given encoding', () { test('when no content-type header is present then it defaults to utf8', () { - expect( - _createMessage( - body: Body.fromString(''), - ).encoding, - utf8); + expect(_createMessage(body: Body.fromString('')).encoding, utf8); }); test('when encoding a String then it defaults to UTF-8', () { expect( - _createMessage( - body: Body.fromString('è'), - ).read().toList(), - completion(equals([ - [195, 168] - ])), + _createMessage(body: Body.fromString('è')).read().toList(), + completion( + equals([ + [195, 168], + ]), + ), ); }); @@ -234,9 +224,11 @@ void main() { _createMessage( body: Body.fromString('è', encoding: latin1), ).read().toList(), - completion(equals([ - [232] - ])), + completion( + equals([ + [232], + ]), + ), ); }); }); diff --git a/test/message/request_test.dart b/test/message/request_test.dart index f78ff504..b66872cb 100644 --- a/test/message/request_test.dart +++ b/test/message/request_test.dart @@ -7,16 +7,8 @@ import 'package:test/test.dart'; import '../util/test_util.dart'; -Request _request({ - final Headers? headers, - final Body? body, -}) { - return Request( - Method.get, - localhostUri, - headers: headers, - body: body, - ); +Request _request({final Headers? headers, final Body? body}) { + return Request(Method.get, localhostUri, headers: headers, body: body); } void main() { @@ -27,110 +19,145 @@ void main() { }); test( - 'when a non-default protocolVersion is provided then it is set correctly', - () { - final request = Request(Method.get, localhostUri, protocolVersion: '1.0'); - expect(request.protocolVersion, '1.0'); - }); + 'when a non-default protocolVersion is provided then it is set correctly', + () { + final request = Request( + Method.get, + localhostUri, + protocolVersion: '1.0', + ); + expect(request.protocolVersion, '1.0'); + }, + ); group('Given a request URL', () { test( - "when no url is provided then it defaults to the requestedUri's relativized path and query", - () { - final request = - Request(Method.get, Uri.parse('http://localhost/foo/bar?q=1')); - expect(request.url, equals(Uri.parse('foo/bar?q=1'))); - }); + "when no url is provided then it defaults to the requestedUri's relativized path and query", + () { + final request = Request( + Method.get, + Uri.parse('http://localhost/foo/bar?q=1'), + ); + expect(request.url, equals(Uri.parse('foo/bar?q=1'))); + }, + ); test('when the URL contains a colon then it is handled correctly', () { - final request = - Request(Method.get, Uri.parse('http://localhost/foo/bar:42')); + final request = Request( + Method.get, + Uri.parse('http://localhost/foo/bar:42'), + ); expect(request.url, equals(Uri.parse('foo/bar:42'))); }); test( - 'when the URL contains a colon in the first segment then it is handled correctly', - () { - final request = - Request(Method.get, Uri.parse('http://localhost/foo:bar/42')); - expect(request.url, equals(Uri.parse('foo%3Abar/42'))); - }); + 'when the URL contains a colon in the first segment then it is handled correctly', + () { + final request = Request( + Method.get, + Uri.parse('http://localhost/foo:bar/42'), + ); + expect(request.url, equals(Uri.parse('foo%3Abar/42'))); + }, + ); test('when the URL contains a slash then it is handled correctly', () { - final request = - Request(Method.get, Uri.parse('http://localhost/foo/bar%2f42')); + final request = Request( + Method.get, + Uri.parse('http://localhost/foo/bar%2f42'), + ); expect(request.url, equals(Uri.parse('foo/bar%2f42'))); }); test('when handlerPath is provided then url is inferred from it', () { final request = Request( - Method.get, Uri.parse('http://localhost/foo/bar?q=1'), - handlerPath: '/foo/'); + Method.get, + Uri.parse('http://localhost/foo/bar?q=1'), + handlerPath: '/foo/', + ); expect(request.url, equals(Uri.parse('bar?q=1'))); }); test('when a specific url is provided then it is used', () { final request = Request( - Method.get, Uri.parse('http://localhost/foo/bar?q=1'), - url: Uri.parse('bar?q=1')); + Method.get, + Uri.parse('http://localhost/foo/bar?q=1'), + url: Uri.parse('bar?q=1'), + ); expect(request.url, equals(Uri.parse('bar?q=1'))); }); test('when an empty url is provided then it is handled correctly', () { final request = Request( - Method.get, Uri.parse('http://localhost/foo/bar'), - url: Uri.parse('')); + Method.get, + Uri.parse('http://localhost/foo/bar'), + url: Uri.parse(''), + ); expect(request.url, equals(Uri.parse(''))); }); }); group('Given a request handlerPath', () { test("when no handlerPath is provided then it defaults to '/'", () { - final request = - Request(Method.get, Uri.parse('http://localhost/foo/bar')); + final request = Request( + Method.get, + Uri.parse('http://localhost/foo/bar'), + ); expect(request.handlerPath, equals('/')); }); test('when url is provided then handlerPath is inferred from it', () { final request = Request( - Method.get, Uri.parse('http://localhost/foo/bar?q=1'), - url: Uri.parse('bar?q=1')); + Method.get, + Uri.parse('http://localhost/foo/bar?q=1'), + url: Uri.parse('bar?q=1'), + ); expect(request.handlerPath, equals('/foo/')); }); test('when a specific handlerPath is provided then it is used', () { final request = Request( - Method.get, Uri.parse('http://localhost/foo/bar?q=1'), - handlerPath: '/foo/'); + Method.get, + Uri.parse('http://localhost/foo/bar?q=1'), + handlerPath: '/foo/', + ); expect(request.handlerPath, equals('/foo/')); }); test( - 'when a handlerPath without a trailing slash is provided then it adds a trailing slash', - () { - final request = Request( - Method.get, Uri.parse('http://localhost/foo/bar?q=1'), - handlerPath: '/foo'); - expect(request.handlerPath, equals('/foo/')); - expect(request.url, equals(Uri.parse('bar?q=1'))); - }); + 'when a handlerPath without a trailing slash is provided then it adds a trailing slash', + () { + final request = Request( + Method.get, + Uri.parse('http://localhost/foo/bar?q=1'), + handlerPath: '/foo', + ); + expect(request.handlerPath, equals('/foo/')); + expect(request.url, equals(Uri.parse('bar?q=1'))); + }, + ); test( - 'when a single slash is provided as handlerPath then it is handled correctly', - () { - final request = Request( - Method.get, Uri.parse('http://localhost/foo/bar?q=1'), - handlerPath: '/'); - expect(request.handlerPath, equals('/')); - expect(request.url, equals(Uri.parse('foo/bar?q=1'))); - }); + 'when a single slash is provided as handlerPath then it is handled correctly', + () { + final request = Request( + Method.get, + Uri.parse('http://localhost/foo/bar?q=1'), + handlerPath: '/', + ); + expect(request.handlerPath, equals('/')); + expect(request.url, equals(Uri.parse('foo/bar?q=1'))); + }, + ); }); group('Given request errors', () { group('Given a requestedUri', () { test('when it is not absolute then it throws an ArgumentError', () { - expect(() => Request(Method.get, Uri.parse('/path')), - throwsArgumentError); + expect( + () => Request(Method.get, Uri.parse('/path')), + throwsArgumentError, + ); }); test('when it has a fragment then it throws an ArgumentError', () { @@ -143,96 +170,140 @@ void main() { group('Given a url', () { test('when it is not relative then it throws an ArgumentError', () { expect(() { - Request(Method.get, Uri.parse('http://localhost/test'), - url: Uri.parse('http://localhost/test')); + Request( + Method.get, + Uri.parse('http://localhost/test'), + url: Uri.parse('http://localhost/test'), + ); }, throwsArgumentError); }); test('when it is root-relative then it throws an ArgumentError', () { expect(() { - Request(Method.get, Uri.parse('http://localhost/test'), - url: Uri.parse('/test')); + Request( + Method.get, + Uri.parse('http://localhost/test'), + url: Uri.parse('/test'), + ); }, throwsArgumentError); }); test('when it has a fragment then it throws an ArgumentError', () { expect(() { - Request(Method.get, Uri.parse('http://localhost/test'), - url: Uri.parse('test#fragment')); + Request( + Method.get, + Uri.parse('http://localhost/test'), + url: Uri.parse('test#fragment'), + ); }, throwsArgumentError); }); test( - 'when it is not a suffix of requestedUri then it throws an ArgumentError', - () { - expect(() { - Request(Method.get, Uri.parse('http://localhost/dir/test'), - url: Uri.parse('dir')); - }, throwsArgumentError); - }); + 'when it is not a suffix of requestedUri then it throws an ArgumentError', + () { + expect(() { + Request( + Method.get, + Uri.parse('http://localhost/dir/test'), + url: Uri.parse('dir'), + ); + }, throwsArgumentError); + }, + ); test( - 'when it does not have the same query parameters as requestedUri then it throws an ArgumentError', - () { - expect(() { - Request(Method.get, Uri.parse('http://localhost/test?q=1&r=2'), - url: Uri.parse('test?q=2&r=1')); - }, throwsArgumentError); - - // Order matters for query parameters. - expect(() { - Request(Method.get, Uri.parse('http://localhost/test?q=1&r=2'), - url: Uri.parse('test?r=2&q=1')); - }, throwsArgumentError); - }); + 'when it does not have the same query parameters as requestedUri then it throws an ArgumentError', + () { + expect(() { + Request( + Method.get, + Uri.parse('http://localhost/test?q=1&r=2'), + url: Uri.parse('test?q=2&r=1'), + ); + }, throwsArgumentError); + + // Order matters for query parameters. + expect(() { + Request( + Method.get, + Uri.parse('http://localhost/test?q=1&r=2'), + url: Uri.parse('test?r=2&q=1'), + ); + }, throwsArgumentError); + }, + ); }); group('Given a handlerPath', () { test( - 'when it is not a prefix of requestedUri then it throws an ArgumentError', - () { - expect(() { - Request(Method.get, Uri.parse('http://localhost/dir/test'), - handlerPath: '/test'); - }, throwsArgumentError); - }); + 'when it is not a prefix of requestedUri then it throws an ArgumentError', + () { + expect(() { + Request( + Method.get, + Uri.parse('http://localhost/dir/test'), + handlerPath: '/test', + ); + }, throwsArgumentError); + }, + ); - test('when it does not start with "/" then it throws an ArgumentError', - () { - expect(() { - Request(Method.get, Uri.parse('http://localhost/test'), - handlerPath: 'test'); - }, throwsArgumentError); - }); + test( + 'when it does not start with "/" then it throws an ArgumentError', + () { + expect(() { + Request( + Method.get, + Uri.parse('http://localhost/test'), + handlerPath: 'test', + ); + }, throwsArgumentError); + }, + ); test( - 'when url is empty and handlerPath is not the requestedUri path then it throws an ArgumentError', - () { - expect(() { - Request(Method.get, Uri.parse('http://localhost/test'), - handlerPath: '/', url: Uri.parse('')); - }, throwsArgumentError); - }); + 'when url is empty and handlerPath is not the requestedUri path then it throws an ArgumentError', + () { + expect(() { + Request( + Method.get, + Uri.parse('http://localhost/test'), + handlerPath: '/', + url: Uri.parse(''), + ); + }, throwsArgumentError); + }, + ); }); group('Given handlerPath + url', () { test( - 'when they do not form the requestedUrl path then it throws an ArgumentError', - () { - expect(() { - Request(Method.get, Uri.parse('http://localhost/foo/bar/baz'), - handlerPath: '/foo/', url: Uri.parse('baz')); - }, throwsArgumentError); - }); + 'when they do not form the requestedUrl path then it throws an ArgumentError', + () { + expect(() { + Request( + Method.get, + Uri.parse('http://localhost/foo/bar/baz'), + handlerPath: '/foo/', + url: Uri.parse('baz'), + ); + }, throwsArgumentError); + }, + ); test( - 'when they are not on a path boundary then it throws an ArgumentError', - () { - expect(() { - Request(Method.get, Uri.parse('http://localhost/foo/bar/baz'), - handlerPath: '/foo/ba', url: Uri.parse('r/baz')); - }, throwsArgumentError); - }); + 'when they are not on a path boundary then it throws an ArgumentError', + () { + expect(() { + Request( + Method.get, + Uri.parse('http://localhost/foo/bar/baz'), + handlerPath: '/foo/ba', + url: Uri.parse('r/baz'), + ); + }, throwsArgumentError); + }, + ); }); }); }); @@ -244,57 +315,67 @@ void main() { }); test('when there is a Last-Modified header then it is set correctly', () { - final request = _request(headers: Headers.build((final mh) { - mh.ifModifiedSince = parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT'); - })); - expect(request.headers.ifModifiedSince, - equals(DateTime.parse('1994-11-06 08:49:37z'))); + final request = _request( + headers: Headers.build((final mh) { + mh.ifModifiedSince = parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT'); + }), + ); + expect( + request.headers.ifModifiedSince, + equals(DateTime.parse('1994-11-06 08:49:37z')), + ); }); }); group('Given a request change', () { test( - 'when no arguments are provided then it returns an instance with equal values', - () { - final controller = StreamController(); - - final uri = Uri.parse('https://test.example.com/static/file.html'); - - final request = Request( - Method.get, - uri, - protocolVersion: '2.0', - headers: - Headers.build((final mh) => mh['header1'] = ['header value 1']), - url: Uri.parse('file.html'), - handlerPath: '/static/', - body: Body.fromDataStream(controller.stream), - ); + 'when no arguments are provided then it returns an instance with equal values', + () { + final controller = StreamController(); - final copy = request.copyWith(); - - expect(copy.method, request.method); - expect(copy.requestedUri, request.requestedUri); - expect(copy.protocolVersion, request.protocolVersion); - expect(copy.headers, same(request.headers)); - expect(copy.headers, same(request.headers)); - expect(copy.url, request.url); - expect(copy.handlerPath, request.handlerPath); - expect(copy.readAsString(), completion('hello, world')); - - controller.add(helloBytes); - return Future(() { - controller - ..add(worldBytes) - ..close(); - }); - }); + final uri = Uri.parse('https://test.example.com/static/file.html'); + + final request = Request( + Method.get, + uri, + protocolVersion: '2.0', + headers: Headers.build( + (final mh) => mh['header1'] = ['header value 1'], + ), + url: Uri.parse('file.html'), + handlerPath: '/static/', + body: Body.fromDataStream(controller.stream), + ); + + final copy = request.copyWith(); + + expect(copy.method, request.method); + expect(copy.requestedUri, request.requestedUri); + expect(copy.protocolVersion, request.protocolVersion); + expect(copy.headers, same(request.headers)); + expect(copy.headers, same(request.headers)); + expect(copy.url, request.url); + expect(copy.handlerPath, request.handlerPath); + expect(copy.readAsString(), completion('hello, world')); + + controller.add(helloBytes); + return Future(() { + controller + ..add(worldBytes) + ..close(); + }); + }, + ); group('Given a path', () { test('when updated then it updates handlerPath and url', () { final uri = Uri.parse('https://test.example.com/static/dir/file.html'); - final request = Request(Method.get, uri, - handlerPath: '/static/', url: Uri.parse('dir/file.html')); + final request = Request( + Method.get, + uri, + handlerPath: '/static/', + url: Uri.parse('dir/file.html'), + ); final copy = request.copyWith(path: 'dir'); expect(copy.handlerPath, '/static/dir/'); @@ -303,8 +384,12 @@ void main() { test('when a trailing slash is allowed then it is handled correctly', () { final uri = Uri.parse('https://test.example.com/static/dir/file.html'); - final request = Request(Method.get, uri, - handlerPath: '/static/', url: Uri.parse('dir/file.html')); + final request = Request( + Method.get, + uri, + handlerPath: '/static/', + url: Uri.parse('dir/file.html'), + ); final copy = request.copyWith(path: 'dir/'); expect(copy.handlerPath, '/static/dir/'); @@ -312,47 +397,72 @@ void main() { }); test( - 'when handling a regression test for Issue #142 then it is handled correctly', - () { - final uri = Uri.parse('https://test.example.com/static/dir/'); - final request = Request(Method.get, uri, - handlerPath: '/static/', url: Uri.parse('dir/')); - - final copy = request.copyWith(path: 'dir'); - expect(copy.handlerPath, '/static/dir/'); - expect(copy.url, Uri.parse('')); - }); + 'when handling a regression test for Issue #142 then it is handled correctly', + () { + final uri = Uri.parse('https://test.example.com/static/dir/'); + final request = Request( + Method.get, + uri, + handlerPath: '/static/', + url: Uri.parse('dir/'), + ); + + final copy = request.copyWith(path: 'dir'); + expect(copy.handlerPath, '/static/dir/'); + expect(copy.url, Uri.parse('')); + }, + ); test( - 'when changing path leading to double // then it is handled correctly', - () { - final uri = Uri.parse('https://test.example.com/some_base//more'); - final request = Request(Method.get, uri, - handlerPath: '', url: Uri.parse('some_base//more')); - - final copy = request.copyWith(path: 'some_base'); - expect(copy.handlerPath, '/some_base/'); - expect(copy.url, Uri.parse('/more')); - }); + 'when changing path leading to double // then it is handled correctly', + () { + final uri = Uri.parse('https://test.example.com/some_base//more'); + final request = Request( + Method.get, + uri, + handlerPath: '', + url: Uri.parse('some_base//more'), + ); + + final copy = request.copyWith(path: 'some_base'); + expect(copy.handlerPath, '/some_base/'); + expect(copy.url, Uri.parse('/more')); + }, + ); test( - 'when path does not match existing uri then it throws an ArgumentError', - () { - final uri = Uri.parse('https://test.example.com/static/dir/file.html'); - final request = Request(Method.get, uri, - handlerPath: '/static/', url: Uri.parse('dir/file.html')); - - expect(() => request.copyWith(path: 'wrong'), throwsArgumentError); - }); - - test("when path isn't a path boundary then it throws an ArgumentError", - () { - final uri = Uri.parse('https://test.example.com/static/dir/file.html'); - final request = Request(Method.get, uri, - handlerPath: '/static/', url: Uri.parse('dir/file.html')); + 'when path does not match existing uri then it throws an ArgumentError', + () { + final uri = Uri.parse( + 'https://test.example.com/static/dir/file.html', + ); + final request = Request( + Method.get, + uri, + handlerPath: '/static/', + url: Uri.parse('dir/file.html'), + ); + + expect(() => request.copyWith(path: 'wrong'), throwsArgumentError); + }, + ); - expect(() => request.copyWith(path: 'di'), throwsArgumentError); - }); + test( + "when path isn't a path boundary then it throws an ArgumentError", + () { + final uri = Uri.parse( + 'https://test.example.com/static/dir/file.html', + ); + final request = Request( + Method.get, + uri, + handlerPath: '/static/', + url: Uri.parse('dir/file.html'), + ); + + expect(() => request.copyWith(path: 'di'), throwsArgumentError); + }, + ); }); test('when the original request is read then it allows reading', () { diff --git a/test/message/response_test.dart b/test/message/response_test.dart index 55f33091..e137f77c 100644 --- a/test/message/response_test.dart +++ b/test/message/response_test.dart @@ -23,49 +23,43 @@ void main() { }); test( - 'Given a response with a Uint8List body when read then it does not copy the body', - () async { - final bytes = Uint8List(10); - final response = Response.ok(body: Body.fromData(bytes)); - expect(response.body.contentLength, 10); - expect(await response.read().single, same(bytes)); - }); + 'Given a response with a Uint8List body when read then it does not copy the body', + () async { + final bytes = Uint8List(10); + final response = Response.ok(body: Body.fromData(bytes)); + expect(response.body.contentLength, 10); + expect(await response.read().single, same(bytes)); + }, + ); test( - 'Given a response with a Stream body when read then it does not copy the body', - () async { - final bytes = Stream.value(Uint8List.fromList([1, 2, 3, 4])); - final response = Response.ok(body: Body.fromDataStream(bytes)); - expect(response.read(), same(bytes)); - }); + 'Given a response with a Stream body when read then it does not copy the body', + () async { + final bytes = Stream.value(Uint8List.fromList([1, 2, 3, 4])); + final response = Response.ok(body: Body.fromDataStream(bytes)); + expect(response.read(), same(bytes)); + }, + ); group('Given a new Response.internalServerError without a body', () { test( - 'when readAsString is called then it sets the body to "Internal Server Error"', - () { - final response = Response.internalServerError(); - expect( - response.readAsString(), - completion(equals('Internal Server Error')), - ); - }); + 'when readAsString is called then it sets the body to "Internal Server Error"', + () { + final response = Response.internalServerError(); + expect( + response.readAsString(), + completion(equals('Internal Server Error')), + ); + }, + ); test('when checked then it sets the content-type header to text/plain', () { final response = Response.internalServerError(); final contentType = response.body.bodyType?.mimeType; final encoding = response.body.bodyType?.encoding; - expect( - contentType?.primaryType, - equals('text'), - ); - expect( - contentType?.subType, - equals('plain'), - ); - expect( - encoding?.name, - equals('utf-8'), - ); + expect(contentType?.primaryType, equals('text')); + expect(contentType?.subType, equals('plain')); + expect(encoding?.name, equals('utf-8')); expect(response.body.contentLength, equals(21)); }); }); @@ -77,29 +71,37 @@ void main() { }); test('when a body is set then it returns the correct body', () { - final response = - Response.badRequest(body: Body.fromString('missing token')); + final response = Response.badRequest( + body: Body.fromString('missing token'), + ); expect(response.readAsString(), completion(equals('missing token'))); }); }); group('Given a Response.unauthorized', () { - test('when a body is set then it returns the correct body and status code', - () { - final response = - Response.unauthorized(body: Body.fromString('request unauthorized')); - expect( - response.readAsString(), completion(equals('request unauthorized'))); - expect(response.statusCode, 401); - }); + test( + 'when a body is set then it returns the correct body and status code', + () { + final response = Response.unauthorized( + body: Body.fromString('request unauthorized'), + ); + expect( + response.readAsString(), + completion(equals('request unauthorized')), + ); + expect(response.statusCode, 401); + }, + ); }); group('Given a Response redirect', () { - test('when a String is used then it sets the location header correctly', - () { - final response = Response.found(Uri.parse('/foo')); - expect(response.headers.location.toString(), equals('/foo')); - }); + test( + 'when a String is used then it sets the location header correctly', + () { + final response = Response.found(Uri.parse('/foo')); + expect(response.headers.location.toString(), equals('/foo')); + }, + ); test('when a Uri is used then it sets the location header correctly', () { final response = Response.found(Uri(path: '/foo')); @@ -110,73 +112,90 @@ void main() { group('Given a response with an Expires header', () { test('when no Expires header is present then expires is null', () { expect( - Response.ok(body: Body.fromString('okay!')).headers.expires, isNull); - }); - - test('when an Expires header is present then it returns the correct date', - () { - expect( - Response.ok( - body: Body.fromString('okay!'), - headers: Headers.build((final mh) => - mh.expires = parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT')), - ).headers.expires, - equals(DateTime.parse('1994-11-06 08:49:37z')), + Response.ok(body: Body.fromString('okay!')).headers.expires, + isNull, ); }); + + test( + 'when an Expires header is present then it returns the correct date', + () { + expect( + Response.ok( + body: Body.fromString('okay!'), + headers: Headers.build( + (final mh) => + mh.expires = parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT'), + ), + ).headers.expires, + equals(DateTime.parse('1994-11-06 08:49:37z')), + ); + }, + ); }); group('Given a response with a Last-Modified header', () { - test('when no Last-Modified header is present then lastModified is null', - () { - expect(Response.ok(body: Body.fromString('okay!')).headers.lastModified, - isNull); - }); + test( + 'when no Last-Modified header is present then lastModified is null', + () { + expect( + Response.ok(body: Body.fromString('okay!')).headers.lastModified, + isNull, + ); + }, + ); test( - 'when a Last-Modified header is present then it returns the correct date', - () { - expect( - Response.ok( - body: Body.fromString('okay!'), - headers: Headers.build((final mh) => - mh.lastModified = parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT')), - ).headers.lastModified, - equals(DateTime.parse('1994-11-06 08:49:37z')), - ); - }); + 'when a Last-Modified header is present then it returns the correct date', + () { + expect( + Response.ok( + body: Body.fromString('okay!'), + headers: Headers.build( + (final mh) => + mh.lastModified = parseHttpDate( + 'Sun, 06 Nov 1994 08:49:37 GMT', + ), + ), + ).headers.lastModified, + equals(DateTime.parse('1994-11-06 08:49:37z')), + ); + }, + ); }); group('Given a response change', () { test( - 'when no arguments are provided then it returns an instance with equal values', - () { - final controller = StreamController(); - - final response = Response( - 345, - body: Body.fromString('hèllo, world'), - encoding: latin1, - headers: - Headers.build((final mh) => mh['header1'] = ['header value 1']), - context: {'context1': 'context value 1'}, - ); - - final copy = response.copyWith(); - - expect(copy.statusCode, response.statusCode); - expect(copy.readAsString(), completion('hèllo, world')); - expect(copy.headers.hashCode, response.headers.hashCode); - expect(copy.headers, same(response.headers)); - expect(copy.encoding, response.encoding); - - controller.add(helloBytes); - return Future(() { - controller - ..add(worldBytes) - ..close(); - }); - }); + 'when no arguments are provided then it returns an instance with equal values', + () { + final controller = StreamController(); + + final response = Response( + 345, + body: Body.fromString('hèllo, world'), + encoding: latin1, + headers: Headers.build( + (final mh) => mh['header1'] = ['header value 1'], + ), + context: {'context1': 'context value 1'}, + ); + + final copy = response.copyWith(); + + expect(copy.statusCode, response.statusCode); + expect(copy.readAsString(), completion('hèllo, world')); + expect(copy.headers.hashCode, response.headers.hashCode); + expect(copy.headers, same(response.headers)); + expect(copy.encoding, response.encoding); + + controller.add(helloBytes); + return Future(() { + controller + ..add(worldBytes) + ..close(); + }); + }, + ); test('when the original response is read then it allows reading', () { final response = Response.ok(body: null); diff --git a/test/middleware/create_middleware_test.dart b/test/middleware/create_middleware_test.dart index 49ff7051..786f2558 100644 --- a/test/middleware/create_middleware_test.dart +++ b/test/middleware/create_middleware_test.dart @@ -9,247 +9,326 @@ import '../util/test_util.dart'; void main() { test( - 'Given a middleware with null handlers when a request is processed then it forwards the request and response', - () async { - final handler = const Pipeline() - .addMiddleware(createMiddleware()) - .addHandler(createSyncHandler( - headers: Headers.build((final mh) => - mh.from = FromHeader(emails: ['innerHandler@serverpod.dev'])), - )); - - final response = await makeSimpleRequest(handler); - expect( - response.headers.from?.emails, contains('innerHandler@serverpod.dev')); - }); + 'Given a middleware with null handlers when a request is processed then it forwards the request and response', + () async { + final handler = const Pipeline() + .addMiddleware(createMiddleware()) + .addHandler( + createSyncHandler( + headers: Headers.build( + (final mh) => + mh.from = FromHeader( + emails: ['innerHandler@serverpod.dev'], + ), + ), + ), + ); + + final response = await makeSimpleRequest(handler); + expect( + response.headers.from?.emails, + contains('innerHandler@serverpod.dev'), + ); + }, + ); group('Given a requestHandler', () { test( - 'when sync null response is returned then it forwards to inner handler', - () async { - final handler = const Pipeline() - .addMiddleware(createMiddleware(onRequest: (final request) => null)) - .addHandler(syncHandler); + 'when sync null response is returned then it forwards to inner handler', + () async { + final handler = const Pipeline() + .addMiddleware(createMiddleware(onRequest: (final request) => null)) + .addHandler(syncHandler); - final response = await makeSimpleRequest(handler); - expect(response.headers['from'], isNull); - }); + final response = await makeSimpleRequest(handler); + expect(response.headers['from'], isNull); + }, + ); test( - 'when async null response is returned then it forwards to inner handler', - () async { - final handler = const Pipeline() - .addMiddleware(createMiddleware( - onRequest: (final request) => Future.value(null))) - .addHandler(syncHandler); + 'when async null response is returned then it forwards to inner handler', + () async { + final handler = const Pipeline() + .addMiddleware( + createMiddleware( + onRequest: (final request) => Future.value(null), + ), + ) + .addHandler(syncHandler); - final response = await makeSimpleRequest(handler); - expect(response.headers.from, isNull); - }); + final response = await makeSimpleRequest(handler); + expect(response.headers.from, isNull); + }, + ); test('when sync response is returned then it is used', () async { final handler = const Pipeline() - .addMiddleware(createMiddleware( - onRequest: (final request) => _middlewareResponse)) + .addMiddleware( + createMiddleware(onRequest: (final request) => _middlewareResponse), + ) .addHandler(respondWith(_failHandler)); final response = await makeSimpleRequest(handler); expect( - response.headers.from?.emails, contains('middleware@serverpod.dev')); + response.headers.from?.emails, + contains('middleware@serverpod.dev'), + ); }); test('when async response is returned then it is used', () async { final handler = const Pipeline() - .addMiddleware(createMiddleware( - onRequest: (final request) => Future.value(_middlewareResponse))) + .addMiddleware( + createMiddleware( + onRequest: (final request) => Future.value(_middlewareResponse), + ), + ) .addHandler(respondWith(_failHandler)); final response = await makeSimpleRequest(handler); expect( - response.headers.from?.emails, contains('middleware@serverpod.dev')); + response.headers.from?.emails, + contains('middleware@serverpod.dev'), + ); }); group('Given a responseHandler', () { - test('when sync result is returned then responseHandler is not called', - () async { - final middleware = createMiddleware( + test( + 'when sync result is returned then responseHandler is not called', + () async { + final middleware = createMiddleware( onRequest: (final request) => _middlewareResponse, - onResponse: (final response) => fail('should not be called')); - - final handler = - const Pipeline().addMiddleware(middleware).addHandler(syncHandler); - - final response = await makeSimpleRequest(handler); - expect(response.headers.from?.emails, - contains('middleware@serverpod.dev')); - }); + onResponse: (final response) => fail('should not be called'), + ); + + final handler = const Pipeline() + .addMiddleware(middleware) + .addHandler(syncHandler); + + final response = await makeSimpleRequest(handler); + expect( + response.headers.from?.emails, + contains('middleware@serverpod.dev'), + ); + }, + ); - test('when async result is returned then responseHandler is not called', - () async { - final middleware = createMiddleware( + test( + 'when async result is returned then responseHandler is not called', + () async { + final middleware = createMiddleware( onRequest: (final request) => Future.value(_middlewareResponse), - onResponse: (final response) => fail('should not be called')); - final handler = - const Pipeline().addMiddleware(middleware).addHandler(syncHandler); - - final response = await makeSimpleRequest(handler); - expect(response.headers.from?.emails, - contains('middleware@serverpod.dev')); - }); + onResponse: (final response) => fail('should not be called'), + ); + final handler = const Pipeline() + .addMiddleware(middleware) + .addHandler(syncHandler); + + final response = await makeSimpleRequest(handler); + expect( + response.headers.from?.emails, + contains('middleware@serverpod.dev'), + ); + }, + ); }); }); group('Given a responseHandler', () { test( - 'when innerHandler sync response is seen then replaced value continues', - () async { - final handler = const Pipeline() - .addMiddleware(createMiddleware(onResponse: (final response) { + 'when innerHandler sync response is seen then replaced value continues', + () async { + final handler = const Pipeline() + .addMiddleware( + createMiddleware( + onResponse: (final response) { + expect( + response.headers.from?.emails, + contains('handler@serverpod.dev'), + ); + return _middlewareResponse; + }, + ), + ) + .addHandler( + createSyncHandler( + headers: Headers.build( + (final mh) => + mh.from = FromHeader(emails: ['handler@serverpod.dev']), + ), + ), + ); + + final response = await makeSimpleRequest(handler); expect( response.headers.from?.emails, - contains('handler@serverpod.dev'), + contains('middleware@serverpod.dev'), ); - return _middlewareResponse; - })).addHandler(createSyncHandler( - headers: Headers.build((final mh) => - mh.from = FromHeader(emails: ['handler@serverpod.dev'])))); + }, + ); - final response = await makeSimpleRequest(handler); - expect( - response.headers.from?.emails, - contains('middleware@serverpod.dev'), - ); - }); + test( + 'when innerHandler async response is seen then async value continues', + () async { + final handler = const Pipeline() + .addMiddleware( + createMiddleware( + onResponse: (final response) { + expect( + response.headers.from?.emails, + contains('handler@serverpod.dev'), + ); + return Future.value(_middlewareResponse); + }, + ), + ) + .addHandler(((final ctx) { + return Future( + () => createSyncHandler( + headers: Headers.build( + (final mh) => + mh.from = FromHeader(emails: ['handler@serverpod.dev']), + ), + )(ctx), + ); + })); - test('when innerHandler async response is seen then async value continues', - () async { - final handler = const Pipeline().addMiddleware( - createMiddleware( - onResponse: (final response) { - expect( - response.headers.from?.emails, - contains('handler@serverpod.dev'), - ); - return Future.value(_middlewareResponse); - }, - ), - ).addHandler(((final ctx) { - return Future( - () => createSyncHandler( - headers: Headers.build((final mh) => - mh.from = FromHeader(emails: ['handler@serverpod.dev'])), - )(ctx), + final response = await makeSimpleRequest(handler); + expect( + response.headers.from?.emails, + contains('middleware@serverpod.dev'), ); - })); - - final response = await makeSimpleRequest(handler); - expect( - response.headers.from?.emails, - contains('middleware@serverpod.dev'), - ); - }); + }, + ); }); group('Given error handling', () { - test('when sync error is thrown by requestHandler then it bubbles down', - () { - final handler = const Pipeline() - .addMiddleware(createMiddleware( - onRequest: (final request) => throw 'middleware error')) - .addHandler(respondWith(_failHandler)); - - expect(makeSimpleRequest(handler), throwsA('middleware error')); - }); - - test('when async error is thrown by requestHandler then it bubbles down', - () { - final handler = const Pipeline() - .addMiddleware(createMiddleware( - onRequest: (final request) => Future.error('middleware error'))) - .addHandler(respondWith(_failHandler)); + test( + 'when sync error is thrown by requestHandler then it bubbles down', + () { + final handler = const Pipeline() + .addMiddleware( + createMiddleware( + onRequest: (final request) => throw 'middleware error', + ), + ) + .addHandler(respondWith(_failHandler)); + + expect(makeSimpleRequest(handler), throwsA('middleware error')); + }, + ); - expect(makeSimpleRequest(handler), throwsA('middleware error')); - }); + test( + 'when async error is thrown by requestHandler then it bubbles down', + () { + final handler = const Pipeline() + .addMiddleware( + createMiddleware( + onRequest: (final request) => Future.error('middleware error'), + ), + ) + .addHandler(respondWith(_failHandler)); + + expect(makeSimpleRequest(handler), throwsA('middleware error')); + }, + ); - test('when throw from responseHandler then it does not hit error handler', - () { - final middleware = createMiddleware( + test( + 'when throw from responseHandler then it does not hit error handler', + () { + final middleware = createMiddleware( onResponse: (final response) { throw 'middleware error'; }, - onError: (final e, final s) => fail('should never get here')); + onError: (final e, final s) => fail('should never get here'), + ); - final handler = - const Pipeline().addMiddleware(middleware).addHandler(syncHandler); + final handler = const Pipeline() + .addMiddleware(middleware) + .addHandler(syncHandler); - expect(makeSimpleRequest(handler), throwsA('middleware error')); - }); + expect(makeSimpleRequest(handler), throwsA('middleware error')); + }, + ); test('when requestHandler throw then it does not hit errorHandlers', () { final middleware = createMiddleware( - onRequest: (final request) { - throw 'middleware error'; - }, - onError: (final e, final s) => fail('should never get here')); + onRequest: (final request) { + throw 'middleware error'; + }, + onError: (final e, final s) => fail('should never get here'), + ); - final handler = - const Pipeline().addMiddleware(middleware).addHandler(syncHandler); + final handler = const Pipeline() + .addMiddleware(middleware) + .addHandler(syncHandler); expect(makeSimpleRequest(handler), throwsA('middleware error')); }); test( - 'when inner handler throws then it is caught by errorHandler with response', - () async { - final middleware = createMiddleware(onError: (final error, final stack) { - expect(error, 'bad handler'); - return _middlewareResponse; - }); + 'when inner handler throws then it is caught by errorHandler with response', + () async { + final middleware = createMiddleware( + onError: (final error, final stack) { + expect(error, 'bad handler'); + return _middlewareResponse; + }, + ); - final handler = const Pipeline() - .addMiddleware(middleware) - .addHandler(respondWith((final request) { - throw 'bad handler'; - })); + final handler = const Pipeline() + .addMiddleware(middleware) + .addHandler( + respondWith((final request) { + throw 'bad handler'; + }), + ); - final response = await makeSimpleRequest(handler); - expect( - response.headers.from?.emails, - contains('middleware@serverpod.dev'), - ); - }); + final response = await makeSimpleRequest(handler); + expect( + response.headers.from?.emails, + contains('middleware@serverpod.dev'), + ); + }, + ); test( - 'when inner handler throws then it is caught by errorHandler and rethrown', - () { - final middleware = - createMiddleware(onError: (final Object error, final stack) { - expect(error, 'bad handler'); - throw error; - }); + 'when inner handler throws then it is caught by errorHandler and rethrown', + () { + final middleware = createMiddleware( + onError: (final Object error, final stack) { + expect(error, 'bad handler'); + throw error; + }, + ); - final handler = const Pipeline() - .addMiddleware(middleware) - .addHandler(respondWith((final request) { - throw 'bad handler'; - })); + final handler = const Pipeline() + .addMiddleware(middleware) + .addHandler( + respondWith((final request) { + throw 'bad handler'; + }), + ); - expect(makeSimpleRequest(handler), throwsA('bad handler')); - }); + expect(makeSimpleRequest(handler), throwsA('bad handler')); + }, + ); test( - 'when error is thrown by inner handler without a middleware errorHandler then it is rethrown', - () { - final middleware = createMiddleware(); - - final handler = const Pipeline() - .addMiddleware(middleware) - .addHandler(respondWith((final request) { - throw 'bad handler'; - })); + 'when error is thrown by inner handler without a middleware errorHandler then it is rethrown', + () { + final middleware = createMiddleware(); + + final handler = const Pipeline() + .addMiddleware(middleware) + .addHandler( + respondWith((final request) { + throw 'bad handler'; + }), + ); - expect(makeSimpleRequest(handler), throwsA('bad handler')); - }); + expect(makeSimpleRequest(handler), throwsA('bad handler')); + }, + ); }); } diff --git a/test/middleware/log_middleware_test.dart b/test/middleware/log_middleware_test.dart index 0823e001..246bd6c7 100644 --- a/test/middleware/log_middleware_test.dart +++ b/test/middleware/log_middleware_test.dart @@ -24,46 +24,50 @@ void main() { } test( - 'Given a request with a synchronous response when logged then it logs the request', - () async { - final handler = const Pipeline() - .addMiddleware(logRequests(logger: logger)) - .addHandler(syncHandler); + 'Given a request with a synchronous response when logged then it logs the request', + () async { + final handler = const Pipeline() + .addMiddleware(logRequests(logger: logger)) + .addHandler(syncHandler); - await makeSimpleRequest(handler); - expect(gotLog, isTrue); - }); + await makeSimpleRequest(handler); + expect(gotLog, isTrue); + }, + ); test( - 'Given a request with an asynchronous response when logged then it logs the request', - () async { - final handler = const Pipeline() - .addMiddleware(logRequests(logger: logger)) - .addHandler(asyncHandler); + 'Given a request with an asynchronous response when logged then it logs the request', + () async { + final handler = const Pipeline() + .addMiddleware(logRequests(logger: logger)) + .addHandler(asyncHandler); - await makeSimpleRequest(handler); - expect(gotLog, isTrue); - }); + await makeSimpleRequest(handler); + expect(gotLog, isTrue); + }, + ); test( 'Given a request with an asynchronous error response when logged then it logs the error', () { - final handler = const Pipeline().addMiddleware(logRequests( - logger: ( - final msg, { - final LoggerType type = LoggerType.info, - final StackTrace? stackTrace, - }) { - expect(gotLog, isFalse); - gotLog = true; - expect(type, LoggerType.error); - expect(msg, contains('oh no')); - }, - )).addHandler( - (final request) { - throw StateError('oh no'); - }, - ); + final handler = const Pipeline() + .addMiddleware( + logRequests( + logger: ( + final msg, { + final LoggerType type = LoggerType.info, + final StackTrace? stackTrace, + }) { + expect(gotLog, isFalse); + gotLog = true; + expect(type, LoggerType.error); + expect(msg, contains('oh no')); + }, + ), + ) + .addHandler((final request) { + throw StateError('oh no'); + }); expect(makeSimpleRequest(handler), throwsA(isOhNoStateError)); }, diff --git a/test/middleware/middleware_object_test.dart b/test/middleware/middleware_object_test.dart index 908cb44c..34cf8ec9 100644 --- a/test/middleware/middleware_object_test.dart +++ b/test/middleware/middleware_object_test.dart @@ -4,8 +4,7 @@ import 'package:test/test.dart'; void main() { group('MiddlewareObject', () { - test( - 'Given a MiddlewareObject, ' + test('Given a MiddlewareObject, ' 'when used as middleware via call, ' 'then it wraps the handler correctly', () async { final middlewareObject = _TestMiddlewareObject(); @@ -21,8 +20,7 @@ void main() { expect(result.response.headers['X-Middleware'], ['applied']); }); - test( - 'Given a MiddlewareObject, ' + test('Given a MiddlewareObject, ' 'when asMiddleware getter is used, ' 'then it returns a valid Middleware function', () async { final middlewareObject = _TestMiddlewareObject(); @@ -43,8 +41,7 @@ void main() { expect(result.response.headers['X-Middleware'], ['applied']); }); - test( - 'Given a MiddlewareObject that modifies requests, ' + test('Given a MiddlewareObject that modifies requests, ' 'when it wraps a handler, ' 'then the handler receives the modified context', () async { final middlewareObject = _RequestModifyingMiddleware(); @@ -64,8 +61,7 @@ void main() { expect(capturedHeader, 'by-middleware'); }); - test( - 'Given a MiddlewareObject that can short-circuit, ' + test('Given a MiddlewareObject that can short-circuit, ' 'when it returns early, ' 'then the inner handler is not called', () async { final middlewareObject = _ShortCircuitMiddleware(); diff --git a/test/middleware/routing_middleware_test.dart b/test/middleware/routing_middleware_test.dart index a7b1659e..9711350e 100644 --- a/test/middleware/routing_middleware_test.dart +++ b/test/middleware/routing_middleware_test.dart @@ -12,9 +12,11 @@ class _FakeRequest extends Fake implements Request { @override final Method method; - _FakeRequest(final String path, - {final String host = 'localhost', this.method = Method.get}) - : url = Uri.parse('http://$host/$path'); + _FakeRequest( + final String path, { + final String host = 'localhost', + this.method = Method.get, + }) : url = Uri.parse('http://$host/$path'); } void main() { @@ -28,8 +30,7 @@ void main() { }); group('Parameter Propagation', () { - test( - 'Given a router with a parameterized route and RoutingMiddleware, ' + test('Given a router with a parameterized route and RoutingMiddleware, ' 'When a request matches the parameterized route, ' 'Then the handler receives correct path parameters', () async { Map? capturedParams; @@ -42,7 +43,8 @@ void main() { final initialCtx = _FakeRequest('/users/123').toContext(Object()); final resultingCtx = await middleware( - respondWith((final _) => Response(404)))(initialCtx); + respondWith((_) => Response(404)), + )(initialCtx); expect(capturedParams, isNotNull); expect(capturedParams, equals({#id: '123'})); @@ -52,48 +54,52 @@ void main() { }); test( - 'Given a router with a non-parameterized route and RoutingMiddleware, ' - 'When a request matches the non-parameterized route, ' - 'Then the handler receives empty path parameters', () async { - Map? capturedParams; - Future testHandler(final NewContext ctx) async { - capturedParams = ctx.pathParameters; - return ctx.respond(Response(200)); - } - - router.add(Method.get, '/users', testHandler); - - final initialCtx = _FakeRequest('/users').toContext(Object()); - final resultingCtx = await middleware( - respondWith((final _) => Response(404)))(initialCtx); - - expect(capturedParams, isNotNull); - expect(capturedParams, isEmpty); - expect(resultingCtx, isA()); - final response = (resultingCtx as ResponseContext).response; - expect(response.statusCode, equals(200)); - }); + 'Given a router with a non-parameterized route and RoutingMiddleware, ' + 'When a request matches the non-parameterized route, ' + 'Then the handler receives empty path parameters', + () async { + Map? capturedParams; + Future testHandler(final NewContext ctx) async { + capturedParams = ctx.pathParameters; + return ctx.respond(Response(200)); + } + + router.add(Method.get, '/users', testHandler); + + final initialCtx = _FakeRequest('/users').toContext(Object()); + final resultingCtx = await middleware( + respondWith((_) => Response(404)), + )(initialCtx); + + expect(capturedParams, isNotNull); + expect(capturedParams, isEmpty); + expect(resultingCtx, isA()); + final response = (resultingCtx as ResponseContext).response; + expect(response.statusCode, equals(200)); + }, + ); test( - 'Given RoutingMiddleware and a request that does not match any route, ' - 'When the middleware processes the request, ' - 'Then the next handler is called and pathParameters is empty', - () async { - bool nextCalled = false; - Future nextHandler(final NewContext ctx) async { - nextCalled = true; - expect(ctx.pathParameters, isEmpty); - return ctx.respond(Response(404)); - } - - final initialCtx = _FakeRequest('/nonexistent').toContext(Object()); - final resultingCtx = await middleware(nextHandler)(initialCtx); - - expect(nextCalled, isTrue); - expect(resultingCtx, isA()); - final response = (resultingCtx as ResponseContext).response; - expect(response.statusCode, equals(404)); - }); + 'Given RoutingMiddleware and a request that does not match any route, ' + 'When the middleware processes the request, ' + 'Then the next handler is called and pathParameters is empty', + () async { + bool nextCalled = false; + Future nextHandler(final NewContext ctx) async { + nextCalled = true; + expect(ctx.pathParameters, isEmpty); + return ctx.respond(Response(404)); + } + + final initialCtx = _FakeRequest('/nonexistent').toContext(Object()); + final resultingCtx = await middleware(nextHandler)(initialCtx); + + expect(nextCalled, isTrue); + expect(resultingCtx, isA()); + final response = (resultingCtx as ResponseContext).response; + expect(response.statusCode, equals(404)); + }, + ); }); group('Multiple RoutingMiddleware in Pipeline', () { @@ -111,90 +117,117 @@ void main() { }); test( - 'Given two RoutingMiddleware instances in a pipeline, ' - 'When a request matches a route in the first router, ' - 'Then the handler from the first router is executed with correct parameters', - () async { - Map? params1; - bool handler1Called = false; - bool handler2Called = false; - - router1.add(Method.get, '/router1/:item', (final NewContext ctx) async { - handler1Called = true; - params1 = ctx.pathParameters; - return ctx.respond(Response(201)); - }); - router2.add(Method.get, '/router2/:item', respondWith((final _) { - handler2Called = true; - return Response(202); - })); - - final pipelineHandler = - pipeline.addHandler(respondWith((final _) => Response(404))); - - final initialCtx = _FakeRequest('/router1/apple').toContext(Object()); - final resultingCtx = await pipelineHandler(initialCtx); - final response = (resultingCtx as ResponseContext).response; - - expect(handler1Called, isTrue); - expect(handler2Called, isFalse); - expect(response.statusCode, equals(201)); - expect(params1, equals({#item: 'apple'})); - }); + 'Given two RoutingMiddleware instances in a pipeline, ' + 'When a request matches a route in the first router, ' + 'Then the handler from the first router is executed with correct parameters', + () async { + Map? params1; + bool handler1Called = false; + bool handler2Called = false; + + router1.add(Method.get, '/router1/:item', ( + final NewContext ctx, + ) async { + handler1Called = true; + params1 = ctx.pathParameters; + return ctx.respond(Response(201)); + }); + router2.add( + Method.get, + '/router2/:item', + respondWith((_) { + handler2Called = true; + return Response(202); + }), + ); + + final pipelineHandler = pipeline.addHandler( + respondWith((_) => Response(404)), + ); + + final initialCtx = _FakeRequest('/router1/apple').toContext(Object()); + final resultingCtx = await pipelineHandler(initialCtx); + final response = (resultingCtx as ResponseContext).response; + + expect(handler1Called, isTrue); + expect(handler2Called, isFalse); + expect(response.statusCode, equals(201)); + expect(params1, equals({#item: 'apple'})); + }, + ); test( - 'Given two RoutingMiddleware instances in a pipeline, ' - 'When a request matches a route in the second router (but not the first), ' - 'Then the handler from the second router is executed with correct parameters', - () async { - Map? params2; - bool handler1Called = false; - bool handler2Called = false; - - router1.add(Method.get, '/router1/:item', respondWith((final _) { - handler1Called = true; - return Response(201); - })); - router2.add(Method.get, '/router2/:data', (final NewContext ctx) async { - handler2Called = true; - params2 = ctx.pathParameters; - return ctx.respond(Response(202)); - }); - - final pipelineHandler = - pipeline.addHandler(respondWith((final _) => Response(404))); - - final initialCtx = _FakeRequest('/router2/banana').toContext(Object()); - final resultingCtx = await pipelineHandler(initialCtx); - final response = (resultingCtx as ResponseContext).response; - - expect(handler1Called, isFalse); - expect(handler2Called, isTrue); - expect(response.statusCode, equals(202)); - expect(params2, equals({#data: 'banana'})); - }); + 'Given two RoutingMiddleware instances in a pipeline, ' + 'When a request matches a route in the second router (but not the first), ' + 'Then the handler from the second router is executed with correct parameters', + () async { + Map? params2; + bool handler1Called = false; + bool handler2Called = false; + + router1.add( + Method.get, + '/router1/:item', + respondWith((_) { + handler1Called = true; + return Response(201); + }), + ); + router2.add(Method.get, '/router2/:data', ( + final NewContext ctx, + ) async { + handler2Called = true; + params2 = ctx.pathParameters; + return ctx.respond(Response(202)); + }); + + final pipelineHandler = pipeline.addHandler( + respondWith((_) => Response(404)), + ); + + final initialCtx = _FakeRequest( + '/router2/banana', + ).toContext(Object()); + final resultingCtx = await pipelineHandler(initialCtx); + final response = (resultingCtx as ResponseContext).response; + + expect(handler1Called, isFalse); + expect(handler2Called, isTrue); + expect(response.statusCode, equals(202)); + expect(params2, equals({#data: 'banana'})); + }, + ); - test( - 'Given two RoutingMiddleware instances in a pipeline, ' + test('Given two RoutingMiddleware instances in a pipeline, ' 'When a request does not match any route in either router, ' 'Then the final next handler is called', () async { bool handler1Called = false; bool handler2Called = false; bool fallbackCalled = false; - router1.add(Method.get, '/router1/:item', respondWith((final _) { - handler1Called = true; - return Response(201); - })); - router2.add(Method.get, '/router2/:data', respondWith((final _) { - handler2Called = true; - return Response(202); - })); - - final pipelineHandler = pipeline.addHandler(respondWith((final _) { - fallbackCalled = true; - return Response(404); - })); + router1.add( + Method.get, + '/router1/:item', + respondWith((_) { + handler1Called = true; + return Response(201); + }), + ); + router2.add( + Method.get, + '/router2/:data', + respondWith((_) { + handler2Called = true; + return Response(202); + }), + ); + + final pipelineHandler = pipeline.addHandler( + respondWith((_) { + fallbackCalled = true; + return Response(404); + }), + ); final initialCtx = _FakeRequest('/neither/nor').toContext(Object()); final resultingCtx = await pipelineHandler(initialCtx); @@ -209,163 +242,181 @@ void main() { group('Nested RoutingMiddleware (via Router.attach)', () { test( - 'Given a main Router with a nested Router attached, and RoutingMiddleware for the main Router, ' - 'When a request matches a route within the nested Router, ' - 'Then the handler from the nested Router is executed with merged path parameters', - () async { - Map? capturedParams; - bool nestedHandlerCalled = false; - - final mainRouter = RelicRouter(); - final nestedRouter = RelicRouter(); - - nestedRouter.add(Method.get, '/details/:detailId', - (final NewContext ctx) async { - nestedHandlerCalled = true; - capturedParams = ctx.pathParameters; - return ctx.respond(Response(200)); - }); - - // Attach nestedRouter to mainRouter under /resource/:resourceId - mainRouter.attach('/resource/:resourceId', nestedRouter); - mainRouter.fallback = respondWith((final _) => Response(404)); - - final pipelineHandler = mainRouter.asHandler; - - final initialCtx = - _FakeRequest('/resource/abc/details/xyz').toContext(Object()); - final resultingCtx = await pipelineHandler(initialCtx); - final response = (resultingCtx as ResponseContext).response; - - expect(nestedHandlerCalled, isTrue); - expect(response.statusCode, equals(200)); - expect(capturedParams, isNotNull); - expect(capturedParams, equals({#resourceId: 'abc', #detailId: 'xyz'})); - }); + 'Given a main Router with a nested Router attached, and RoutingMiddleware for the main Router, ' + 'When a request matches a route within the nested Router, ' + 'Then the handler from the nested Router is executed with merged path parameters', + () async { + Map? capturedParams; + bool nestedHandlerCalled = false; + + final mainRouter = RelicRouter(); + final nestedRouter = RelicRouter(); + + nestedRouter.add(Method.get, '/details/:detailId', ( + final NewContext ctx, + ) async { + nestedHandlerCalled = true; + capturedParams = ctx.pathParameters; + return ctx.respond(Response(200)); + }); + + // Attach nestedRouter to mainRouter under /resource/:resourceId + mainRouter.attach('/resource/:resourceId', nestedRouter); + mainRouter.fallback = respondWith((_) => Response(404)); + + final pipelineHandler = mainRouter.asHandler; + + final initialCtx = _FakeRequest( + '/resource/abc/details/xyz', + ).toContext(Object()); + final resultingCtx = await pipelineHandler(initialCtx); + final response = (resultingCtx as ResponseContext).response; + + expect(nestedHandlerCalled, isTrue); + expect(response.statusCode, equals(200)); + expect(capturedParams, isNotNull); + expect( + capturedParams, + equals({#resourceId: 'abc', #detailId: 'xyz'}), + ); + }, + ); test( - 'Given a main Router with a nested Router (that itself has parameters at its root) attached, ' - 'When a request matches, ' - 'Then parameters from both levels are correctly captured', () async { - // This test addresses the user's note about potential errors in nested routing. - // The key is that Router.lookup should correctly merge parameters. - Map? capturedParams; - bool deeplyNestedHandlerCalled = false; - - final mainRouter = RelicRouter(); - final intermediateRouter = - RelicRouter(); // Will be attached to mainRouter - final leafRouter = - RelicRouter(); // Will be attached to intermediateRouter - - // Define handler for the leaf router - leafRouter.add(Method.get, '/action/:actionName', - (final NewContext ctx) async { - deeplyNestedHandlerCalled = true; - capturedParams = ctx.pathParameters; - return ctx.respond(Response(200)); - }); - - // Attach leafRouter to intermediateRouter under a parameterized path - intermediateRouter.attach('/:intermediateId', leafRouter); - - // Attach intermediateRouter to mainRouter under a parameterized path - mainRouter.attach('/base/:baseId', intermediateRouter); - mainRouter.fallback = respondWith((final _) => Response(404)); - - final pipelineHandler = mainRouter.asHandler; - - final initialCtx = _FakeRequest('/base/b123/i456/action/doSomething') - .toContext(Object()); - final resultingCtx = await pipelineHandler(initialCtx); - final response = (resultingCtx as ResponseContext).response; - - expect(deeplyNestedHandlerCalled, isTrue); - expect(response.statusCode, equals(200)); - expect(capturedParams, isNotNull); - expect( + 'Given a main Router with a nested Router (that itself has parameters at its root) attached, ' + 'When a request matches, ' + 'Then parameters from both levels are correctly captured', + () async { + // This test addresses the user's note about potential errors in nested routing. + // The key is that Router.lookup should correctly merge parameters. + Map? capturedParams; + bool deeplyNestedHandlerCalled = false; + + final mainRouter = RelicRouter(); + final intermediateRouter = + RelicRouter(); // Will be attached to mainRouter + final leafRouter = + RelicRouter(); // Will be attached to intermediateRouter + + // Define handler for the leaf router + leafRouter.add(Method.get, '/action/:actionName', ( + final NewContext ctx, + ) async { + deeplyNestedHandlerCalled = true; + capturedParams = ctx.pathParameters; + return ctx.respond(Response(200)); + }); + + // Attach leafRouter to intermediateRouter under a parameterized path + intermediateRouter.attach('/:intermediateId', leafRouter); + + // Attach intermediateRouter to mainRouter under a parameterized path + mainRouter.attach('/base/:baseId', intermediateRouter); + mainRouter.fallback = respondWith((_) => Response(404)); + + final pipelineHandler = mainRouter.asHandler; + + final initialCtx = _FakeRequest( + '/base/b123/i456/action/doSomething', + ).toContext(Object()); + final resultingCtx = await pipelineHandler(initialCtx); + final response = (resultingCtx as ResponseContext).response; + + expect(deeplyNestedHandlerCalled, isTrue); + expect(response.statusCode, equals(200)); + expect(capturedParams, isNotNull); + expect( capturedParams, equals({ #baseId: 'b123', #intermediateId: 'i456', - #actionName: 'doSomething' - })); - }); + #actionName: 'doSomething', + }), + ); + }, + ); test( - 'Given a path with repeated parameters at different levels introduced by attach, ' - 'When looked up via RoutingMiddleware, ' - 'Then last extracted parameter wins (consistent with PathTrie behavior)', - () async { - Map? capturedParams; - final mainRouter = RelicRouter(); - final subRouter = RelicRouter(); - - subRouter.add(Method.get, '/:id/end', (final NewContext ctx) async { - // sub-router uses :id - capturedParams = ctx.pathParameters; - return ctx.respond(Response(200)); - }); - - mainRouter.attach('/:id/sub', subRouter); // main router uses :id - mainRouter.fallback = respondWith((final _) => Response(404)); - - final pipeline = mainRouter.asHandler; - - final initialCtx = _FakeRequest('/123/sub/456/end').toContext(Object()); - final resultingCtx = await pipeline(initialCtx); - final response = (resultingCtx as ResponseContext).response; - - expect(response.statusCode, 200); - expect(capturedParams, isNotNull); - // PathTrie's behavior is that the parameter from the deeper segment wins. - // Full path: /:id/sub/:id/end -> /123/sub/456/end - // Parameters: {#id: '123', #id: '456'} -> {#id: '456'} - expect(capturedParams, equals({#id: '456'})); - }); + 'Given a path with repeated parameters at different levels introduced by attach, ' + 'When looked up via RoutingMiddleware, ' + 'Then last extracted parameter wins (consistent with PathTrie behavior)', + () async { + Map? capturedParams; + final mainRouter = RelicRouter(); + final subRouter = RelicRouter(); + + subRouter.add(Method.get, '/:id/end', (final NewContext ctx) async { + // sub-router uses :id + capturedParams = ctx.pathParameters; + return ctx.respond(Response(200)); + }); + + mainRouter.attach('/:id/sub', subRouter); // main router uses :id + mainRouter.fallback = respondWith((_) => Response(404)); + + final pipeline = mainRouter.asHandler; + + final initialCtx = _FakeRequest( + '/123/sub/456/end', + ).toContext(Object()); + final resultingCtx = await pipeline(initialCtx); + final response = (resultingCtx as ResponseContext).response; + + expect(response.statusCode, 200); + expect(capturedParams, isNotNull); + // PathTrie's behavior is that the parameter from the deeper segment wins. + // Full path: /:id/sub/:id/end -> /123/sub/456/end + // Parameters: {#id: '123', #id: '456'} -> {#id: '456'} + expect(capturedParams, equals({#id: '456'})); + }, + ); }); }); - test( - 'Given `routeWith` adapting a `Router`, ' - 'When a request matches a route, ' - "Then the `toHandler` processes the route's string value", - () async { - final strRouter = Router()..add(Method.get, '/', 'Hurrah!'); - final mw = routeWith( - strRouter, - toHandler: (final s) => - respondWith((final _) => Response.ok(body: Body.fromString(s))), - ); - - final ctx = _FakeRequest('/').toContext(Object()); - final resCtx = - await mw(respondWith((final _) => Response.notFound()))(ctx) - as ResponseContext; - - expect(resCtx.response.statusCode, 200); - expect(await resCtx.response.readAsString(), 'Hurrah!'); - }, - ); + test('Given `routeWith` adapting a `Router`, ' + 'When a request matches a route, ' + "Then the `toHandler` processes the route's string value", () async { + final strRouter = Router()..add(Method.get, '/', 'Hurrah!'); + final mw = routeWith( + strRouter, + toHandler: + (final s) => + respondWith((_) => Response.ok(body: Body.fromString(s))), + ); + + final ctx = _FakeRequest('/').toContext(Object()); + final resCtx = + await mw(respondWith((_) => Response.notFound()))(ctx) + as ResponseContext; + + expect(resCtx.response.statusCode, 200); + expect(await resCtx.response.readAsString(), 'Hurrah!'); + }); // Due to the decoupling of Router a mapping has to happen // for verbs. These test ensures all mappings are exercised. parameterizedTest( variants: Method.values, - (final v) => 'Given a route for verb: "${v.value}", ' + (final v) => + 'Given a route for verb: "${v.value}", ' 'when responding, ' 'then the request.method is "$v"', (final v) async { late Method method; - final middleware = routeWith(RelicRouter() - ..add(v, '/', respondWith((final req) { - method = req.method; - return Response.ok(); - }))); + final middleware = routeWith( + RelicRouter()..add( + v, + '/', + respondWith((final req) { + method = req.method; + return Response.ok(); + }), + ), + ); final request = _FakeRequest('/', method: v); - final newCtx = - await middleware(respondWith((final _) => Response.notFound()))( - request.toContext(Object())); + final newCtx = await middleware(respondWith((_) => Response.notFound()))( + request.toContext(Object()), + ); expect(newCtx, isA()); final response = (newCtx as ResponseContext).response; expect(response.statusCode, 200); @@ -382,32 +433,36 @@ void main() { middleware = routeWith(router); }); - test( - 'Given a router with GET route only, ' + test('Given a router with GET route only, ' 'when a POST request is made to the same path, ' 'then a 405 response is returned', () async { - router.add(Method.get, '/users', respondWith((final _) => Response(200))); - - final initialCtx = - _FakeRequest('/users', method: Method.post).toContext(Object()); - final resultingCtx = - await middleware(respondWith((final _) => Response(404)))(initialCtx); + router.add(Method.get, '/users', respondWith((_) => Response(200))); + + final initialCtx = _FakeRequest( + '/users', + method: Method.post, + ).toContext(Object()); + final resultingCtx = await middleware(respondWith((_) => Response(404)))( + initialCtx, + ); expect(resultingCtx, isA()); final response = (resultingCtx as ResponseContext).response; expect(response.statusCode, 405); }); - test( - 'Given a router with GET route only, ' + test('Given a router with GET route only, ' 'when a POST request is made to the same path, ' 'then the Allow header contains GET', () async { - router.add(Method.get, '/users', respondWith((final _) => Response(200))); - - final initialCtx = - _FakeRequest('/users', method: Method.post).toContext(Object()); - final resultingCtx = - await middleware(respondWith((final _) => Response(404)))(initialCtx); + router.add(Method.get, '/users', respondWith((_) => Response(200))); + + final initialCtx = _FakeRequest( + '/users', + method: Method.post, + ).toContext(Object()); + final resultingCtx = await middleware(respondWith((_) => Response(404)))( + initialCtx, + ); expect(resultingCtx, isA()); final response = (resultingCtx as ResponseContext).response; @@ -415,18 +470,19 @@ void main() { expect(response.headers.allow, contains(Method.get)); }); - test( - 'Given a router with GET and POST routes for the same path, ' + test('Given a router with GET and POST routes for the same path, ' 'when a PUT request is made to that path, ' 'then the Allow header contains both GET and POST', () async { - router.add(Method.get, '/users', respondWith((final _) => Response(200))); - router.add( - Method.post, '/users', respondWith((final _) => Response(201))); - - final initialCtx = - _FakeRequest('/users', method: Method.put).toContext(Object()); - final resultingCtx = - await middleware(respondWith((final _) => Response(404)))(initialCtx); + router.add(Method.get, '/users', respondWith((_) => Response(200))); + router.add(Method.post, '/users', respondWith((_) => Response(201))); + + final initialCtx = _FakeRequest( + '/users', + method: Method.put, + ).toContext(Object()); + final resultingCtx = await middleware(respondWith((_) => Response(404)))( + initialCtx, + ); expect(resultingCtx, isA()); final response = (resultingCtx as ResponseContext).response; @@ -435,19 +491,23 @@ void main() { expect(allowedMethods, unorderedEquals([Method.get, Method.post])); }); - test( - 'Given a router with multiple HTTP methods for a parameterized route, ' + test('Given a router with multiple HTTP methods for a parameterized route, ' 'when a non-matching method is used with valid parameters, ' 'then a 405 response is returned with correct Allow header', () async { + router.add(Method.get, '/users/:id', respondWith((_) => Response(200))); router.add( - Method.get, '/users/:id', respondWith((final _) => Response(200))); - router.add( - Method.delete, '/users/:id', respondWith((final _) => Response(204))); + Method.delete, + '/users/:id', + respondWith((_) => Response(204)), + ); - final initialCtx = - _FakeRequest('/users/123', method: Method.patch).toContext(Object()); - final resultingCtx = - await middleware(respondWith((final _) => Response(404)))(initialCtx); + final initialCtx = _FakeRequest( + '/users/123', + method: Method.patch, + ).toContext(Object()); + final resultingCtx = await middleware(respondWith((_) => Response(404)))( + initialCtx, + ); expect(resultingCtx, isA()); final response = (resultingCtx as ResponseContext).response; @@ -456,15 +516,16 @@ void main() { expect(allowedMethods, unorderedEquals([Method.get, Method.delete])); }); - test( - 'Given a router with routes that do not match the requested path, ' + test('Given a router with routes that do not match the requested path, ' 'when a request is made, ' 'then next handler is called (path miss, not 405)', () async { - router.add(Method.get, '/users', respondWith((final _) => Response(200))); + router.add(Method.get, '/users', respondWith((_) => Response(200))); bool nextCalled = false; - final initialCtx = - _FakeRequest('/posts', method: Method.get).toContext(Object()); + final initialCtx = _FakeRequest( + '/posts', + method: Method.get, + ).toContext(Object()); final resultingCtx = await middleware((final ctx) async { nextCalled = true; return ctx.respond(Response(404)); diff --git a/test/relic_server_serve_test.dart b/test/relic_server_serve_test.dart index d28455e5..3c70f4a0 100644 --- a/test/relic_server_serve_test.dart +++ b/test/relic_server_serve_test.dart @@ -116,10 +116,12 @@ void main() { }); test('custom status code is received by the client', () async { - await _scheduleServer((createSyncHandler( - statusCode: 299, - body: Body.fromString('Hello from /'), - ))); + await _scheduleServer( + (createSyncHandler( + statusCode: 299, + body: Body.fromString('Hello from /'), + )), + ); final response = await _get(); expect(response.statusCode, 299); @@ -133,29 +135,20 @@ void main() { ); await _scheduleServer((final ctx) { final request = ctx.request; - expect( - request.headers, - containsPair('custom-header', ['client value']), - ); + expect(request.headers, containsPair('custom-header', ['client value'])); // dart:io HttpServer splits multi-value headers into an array // validate that they are combined correctly - expect( - request.headers, - containsPair('multi-header', ['foo,bar,baz']), - ); + expect(request.headers, containsPair('multi-header', ['foo,bar,baz'])); - expect( - multi[request.headers].value, - ['foo', 'bar', 'baz'], - ); + expect(multi[request.headers].value, ['foo', 'bar', 'baz']); return syncHandler(ctx); }); final headers = { 'custom-header': 'client value', - 'multi-header': 'foo,bar,baz' + 'multi-header': 'foo,bar,baz', }; final response = await _get(headers: headers); @@ -208,69 +201,77 @@ void main() { expect(request.method, Method.post); - return ctx.hijack(expectAsync1((final channel) { - expect(channel.stream.first, completion(equals('Hello'.codeUnits))); - - channel.sink.add('HTTP/1.1 404 Not Found\r\n' - 'date: Mon, 23 May 2005 22:38:34 GMT\r\n' - 'Content-Length: 13\r\n' - '\r\n' - 'Hello, world!' - .codeUnits); - channel.sink.close(); - })); + return ctx.hijack( + expectAsync1((final channel) { + expect(channel.stream.first, completion(equals('Hello'.codeUnits))); + + channel.sink.add( + 'HTTP/1.1 404 Not Found\r\n' + 'date: Mon, 23 May 2005 22:38:34 GMT\r\n' + 'Content-Length: 13\r\n' + '\r\n' + 'Hello, world!' + .codeUnits, + ); + channel.sink.close(); + }), + ); }); final response = await _post(body: 'Hello'); expect(response.statusCode, HttpStatus.notFound); expect(response.headers['date'], 'Mon, 23 May 2005 22:38:34 GMT'); expect( - response.stream.bytesToString(), completion(equals('Hello, world!'))); + response.stream.bytesToString(), + completion(equals('Hello, world!')), + ); }); test('supports web socket connetions', () async { await _scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) async { - await for (final e in serverSocket.events) { - expect(e, TextDataReceived('Hello')); - serverSocket.sendText('Hello, world!'); - await serverSocket.close(); - } - })); + return ctx.connect( + expectAsync1((final serverSocket) async { + await for (final e in serverSocket.events) { + expect(e, TextDataReceived('Hello')); + serverSocket.sendText('Hello, world!'); + await serverSocket.close(); + } + }), + ); }); - final ws = - await WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')); + final ws = await WebSocket.connect( + Uri.parse('ws://localhost:$_serverPort'), + ); ws.sendText('Hello'); expect(ws.events.first, completion(TextDataReceived('Hello, world!'))); }); test('passes asynchronous exceptions to the parent error zone', () async { - await runZonedGuarded(() async { - final server = await testServe( - (final ctx) { + await runZonedGuarded( + () async { + final server = await testServe((final ctx) { Future(() => throw StateError('oh no')); return syncHandler(ctx); - }, - ); + }); - final response = await http.get(server.url); - expect(response.statusCode, HttpStatus.ok); - expect(response.body, 'Hello from /'); - await server.close(); - }, expectAsync2((final error, final stack) { - expect(error, isOhNoStateError); - })); + final response = await http.get(server.url); + expect(response.statusCode, HttpStatus.ok); + expect(response.body, 'Hello from /'); + await server.close(); + }, + expectAsync2((final error, final stack) { + expect(error, isOhNoStateError); + }), + ); }); test("doesn't pass asynchronous exceptions to the root error zone", () async { final response = await Zone.root.run(() async { - final server = await testServe( - (final request) { - Future(() => throw StateError('oh no')); - return syncHandler(request); - }, - ); + final server = await testServe((final request) { + Future(() => throw StateError('oh no')); + return syncHandler(request); + }); try { return await http.get(server.url); @@ -333,10 +334,12 @@ void main() { test('defers to header in response', () async { final date = DateTime.utc(1981, 6, 5); - await _scheduleServer(createSyncHandler( - body: Body.fromString('test'), - headers: Headers.build((final mh) => mh.date = date), - )); + await _scheduleServer( + createSyncHandler( + body: Body.fromString('test'), + headers: Headers.build((final mh) => mh.date = date), + ), + ); final response = await _get(); expect(response.headers, contains('date')); @@ -351,39 +354,34 @@ void main() { await _scheduleServer(syncHandler); final response = await _get(); - expect( - response.headers[poweredBy], - isNull, - ); + expect(response.headers[poweredBy], isNull); }); test('can be set manually in response headers', () async { - await _scheduleServer(respondWith((final request) { - return Response.ok( - body: Body.fromString('test'), - headers: Headers.build((final mh) => mh.xPoweredBy = 'myServer'), - ); - })); + await _scheduleServer( + respondWith((final request) { + return Response.ok( + body: Body.fromString('test'), + headers: Headers.build((final mh) => mh.xPoweredBy = 'myServer'), + ); + }), + ); final response = await _get(); expect(response.headers, containsPair(poweredBy, 'myServer')); }); test('is not set by default at server level', () async { - _server = await testServe( - syncHandler, - ); + _server = await testServe(syncHandler); final response = await _get(); - expect( - response.headers[poweredBy], - isNull, - ); + expect(response.headers[poweredBy], isNull); }); test('preserves manually set header in response', () async { _server = await testServe( createSyncHandler( - headers: Headers.build((final mh) => mh.xPoweredBy = 'myServer')), + headers: Headers.build((final mh) => mh.xPoweredBy = 'myServer'), + ), ); final response = await _get(); @@ -392,59 +390,73 @@ void main() { }); test( - 'Given a response with a chunked transfer encoding header and an empty body ' - 'when applying headers ' - 'then the chunked transfer encoding header is removed from the response', - () async { - await _scheduleServer( - createSyncHandler( - body: Body.empty(), - headers: Headers.build((final mh) => mh.transferEncoding = - TransferEncodingHeader(encodings: [TransferEncoding.chunked])), - ), - ); - - final response = await _get(); - expect(response.body, isEmpty); - expect(response.headers['transfer-encoding'], isNull); - }); + 'Given a response with a chunked transfer encoding header and an empty body ' + 'when applying headers ' + 'then the chunked transfer encoding header is removed from the response', + () async { + await _scheduleServer( + createSyncHandler( + body: Body.empty(), + headers: Headers.build( + (final mh) => + mh.transferEncoding = TransferEncodingHeader( + encodings: [TransferEncoding.chunked], + ), + ), + ), + ); - test('respects the "buffer_output" context parameter', () async { - final controller = StreamController(); - await _scheduleServer(respondWith((final request) { - controller.add('Hello, '); + final response = await _get(); + expect(response.body, isEmpty); + expect(response.headers['transfer-encoding'], isNull); + }, + ); - return Response.ok( - body: Body.fromDataStream( - utf8.encoder - .bind(controller.stream) - .map((final list) => Uint8List.fromList(list)), - ), - context: {'buffer_output': false}, + test( + 'respects the "buffer_output" context parameter', + () async { + final controller = StreamController(); + await _scheduleServer( + respondWith((final request) { + controller.add('Hello, '); + + return Response.ok( + body: Body.fromDataStream( + utf8.encoder + .bind(controller.stream) + .map((final list) => Uint8List.fromList(list)), + ), + context: {'buffer_output': false}, + ); + }), ); - })); - final request = - http.Request(Method.get.value, Uri.http('localhost:$_serverPort', '')); + final request = http.Request( + Method.get.value, + Uri.http('localhost:$_serverPort', ''), + ); - final response = await request.send(); - final stream = StreamQueue(utf8.decoder.bind(response.stream)); + final response = await request.send(); + final stream = StreamQueue(utf8.decoder.bind(response.stream)); - var data = await stream.next; - expect(data, equals('Hello, ')); - controller.add('world!'); + var data = await stream.next; + expect(data, equals('Hello, ')); + controller.add('world!'); - data = await stream.next; - expect(data, equals('world!')); - await controller.close(); - expect(stream.hasNext, completion(isFalse)); - }, skip: 'TODO: Find another way to probagate buffer_output'); + data = await stream.next; + expect(data, equals('world!')); + await controller.close(); + expect(stream.hasNext, completion(isFalse)); + }, + skip: 'TODO: Find another way to probagate buffer_output', + ); group('ssl tests', () { - final securityContext = SecurityContext() - ..setTrustedCertificatesBytes(certChainBytes) - ..useCertificateChainBytes(certChainBytes) - ..usePrivateKeyBytes(certKeyBytes, password: 'dartdart'); + final securityContext = + SecurityContext() + ..setTrustedCertificatesBytes(certChainBytes) + ..useCertificateChainBytes(certChainBytes) + ..usePrivateKeyBytes(certKeyBytes, password: 'dartdart'); final sslClient = HttpClient(context: securityContext); @@ -458,8 +470,10 @@ void main() { final response = await req.close(); expect(response.statusCode, HttpStatus.ok); - expect(await response.cast>().transform(utf8.decoder).single, - 'Hello from /'); + expect( + await response.cast>().transform(utf8.decoder).single, + 'Hello from /', + ); }); test('secure async handler returns a value to the client', () async { @@ -485,10 +499,7 @@ Future _scheduleServer( final SecurityContext? securityContext, }) async { assert(_server == null); - _server = await testServe( - handler, - context: securityContext, - ); + _server = await testServe(handler, context: securityContext); } Future _get({ @@ -503,8 +514,9 @@ Future _get({ if (headers != null) request.headers.addAll(headers); final response = await request.send(); - return await http.Response.fromStream(response) - .timeout(const Duration(seconds: 1)); + return await http.Response.fromStream( + response, + ).timeout(const Duration(seconds: 1)); } Future _post({ diff --git a/test/relic_server_test.dart b/test/relic_server_test.dart index 51c5026b..58f728c2 100644 --- a/test/relic_server_test.dart +++ b/test/relic_server_test.dart @@ -19,8 +19,7 @@ void main() { tearDown(() => server.close()); group('Given a server', () { - test( - 'when a valid HTTP request is made ' + test('when a valid HTTP request is made ' 'then it serves the request using the mounted handler', () async { await server.mountAndStart(syncHandler); // Use toUri to ensure we have a valid Uri object @@ -28,18 +27,17 @@ void main() { expect(response, equals('Hello from /')); }); - test( - 'when a malformed HTTP request is made ' + test('when a malformed HTTP request is made ' 'then it returns a 400 Bad Request response', () async { await server.mountAndStart(syncHandler); - final rs = await http - .get(Uri.parse('${server.url}/%D0%C2%BD%A8%CE%C4%BC%FE%BC%D0.zip')); + final rs = await http.get( + Uri.parse('${server.url}/%D0%C2%BD%A8%CE%C4%BC%FE%BC%D0.zip'), + ); expect(rs.statusCode, 400); expect(rs.body, 'Bad Request'); }); - test( - 'when no handler is mounted initially ' + test('when no handler is mounted initially ' 'then it delays requests until a handler is mounted', () async { final adapter = await IOAdapter.bind(InternetAddress.loopbackIPv4); final port = adapter.port; diff --git a/test/router/lru_cache_test.dart b/test/router/lru_cache_test.dart index 4e4e20ed..757964e1 100644 --- a/test/router/lru_cache_test.dart +++ b/test/router/lru_cache_test.dart @@ -2,15 +2,13 @@ import 'package:relic/src/router/lru_cache.dart'; import 'package:test/test.dart'; void main() { - test( - 'Given a negative capacity' + test('Given a negative capacity' 'when cache is created ' 'then it fails', () { expect(() => LruCache(-1), throwsArgumentError); }); - test( - 'Given a positive capacity, ' + test('Given a positive capacity, ' 'when cache is created ' 'then length is 0', () { const capacity = 5; @@ -25,8 +23,7 @@ void main() { cache = LruCache(100); }); - test( - 'when items are put ' + test('when items are put ' 'then they can be retrieved', () { cache['a'] = 1; cache['b'] = 2; @@ -36,8 +33,7 @@ void main() { expect(cache.length, equals(2)); }); - test( - 'when putting the same key with a new value ' + test('when putting the same key with a new value ' 'then the value is updated', () { cache['a'] = 1; cache['a'] = 10; @@ -45,8 +41,7 @@ void main() { expect(cache.length, equals(1)); }); - test( - 'when the same key is put multiple times consecutively ' + test('when the same key is put multiple times consecutively ' 'then the last value is stored', () { cache['a'] = 1; cache['a'] = 10; @@ -107,8 +102,7 @@ void main() { cache['b'] = 2; // MRU }); - test( - 'when a new item is put ' + test('when a new item is put ' 'then the least recently used item is evicted', () { cache['c'] = 3; // Add 'c', evict 'a' @@ -126,8 +120,7 @@ void main() { cache = LruCache(1); }); - test( - 'when multiple items are put ' + test('when multiple items are put ' 'then only the last item remains', () { cache['a'] = 1; cache['b'] = 2; // Evicts 'a' @@ -137,8 +130,7 @@ void main() { expect(cache.length, equals(1)); }); - test( - 'when an item is accessed and then a new item is put ' + test('when an item is accessed and then a new item is put ' 'then the accessed item is evicted', () { cache['b'] = 2; // Start with 'b' final _ = cache['b']; // Access 'b' (doesn't change much with capacity 1) diff --git a/test/router/normalized_path_test.dart b/test/router/normalized_path_test.dart index 955d6267..192f3609 100644 --- a/test/router/normalized_path_test.dart +++ b/test/router/normalized_path_test.dart @@ -4,8 +4,7 @@ import 'package:test/test.dart'; void main() { group('Normalization Logic', () { - test( - 'Given a simple path, ' + test('Given a simple path, ' 'when normalized, ' 'then segments are correct', () { final path = NormalizedPath('a/b/c'); @@ -13,8 +12,7 @@ void main() { expect(path.toString(), equals('/a/b/c')); }); - test( - 'Given path with leading slash, ' + test('Given path with leading slash, ' 'when normalized, ' 'then segments are correct', () { final path = NormalizedPath('/a/b/c'); @@ -22,8 +20,7 @@ void main() { expect(path.toString(), equals('/a/b/c')); }); - test( - 'Given path with trailing slash, ' + test('Given path with trailing slash, ' 'when normalized, ' 'then trailing slash is ignored', () { final path = NormalizedPath('a/b/c/'); @@ -31,8 +28,7 @@ void main() { expect(path.toString(), equals('/a/b/c')); }); - test( - 'Given path with "." segments, ' + test('Given path with "." segments, ' 'when normalized, ' 'then "." segments are removed', () { final path = NormalizedPath('/a/./b/./c'); @@ -40,8 +36,7 @@ void main() { expect(path.toString(), equals('/a/b/c')); }); - test( - 'Given path with ".." segments, ' + test('Given path with ".." segments, ' 'when normalized, ' 'then ".." navigates up', () { final path = NormalizedPath('/a/b/../c'); @@ -49,8 +44,7 @@ void main() { expect(path.toString(), equals('/a/c')); }); - test( - 'Given path with ".." segments at start, ' + test('Given path with ".." segments at start, ' 'when normalized, ' 'then ".." is ignored', () { final path = NormalizedPath('../a/b'); @@ -58,8 +52,7 @@ void main() { expect(path.toString(), equals('/a/b')); }); - test( - 'Given path with excessive ".." segments, ' + test('Given path with excessive ".." segments, ' 'when normalized, ' 'then it stops at root', () { final path = NormalizedPath('/a/../../b'); @@ -67,8 +60,7 @@ void main() { expect(path.toString(), equals('/b')); }); - test( - 'Given path with multiple consecutive slashes, ' + test('Given path with multiple consecutive slashes, ' 'when normalized, ' 'then they are treated as one', () { final path = NormalizedPath('a///b//c'); @@ -76,8 +68,7 @@ void main() { expect(path.toString(), equals('/a/b/c')); }); - test( - 'Given an empty path string, ' + test('Given an empty path string, ' 'when normalized, ' 'then results in root', () { final path = NormalizedPath(''); @@ -85,8 +76,7 @@ void main() { expect(path.toString(), equals('/')); }); - test( - 'Given a path string with only slashes, ' + test('Given a path string with only slashes, ' 'when normalized, ' 'then results in root', () { final path = NormalizedPath('///'); @@ -94,8 +84,7 @@ void main() { expect(path.path, equals('/')); }); - test( - 'Given a path string with only dots and slashes, ' + test('Given a path string with only dots and slashes, ' 'when normalized, ' 'then results in root', () { final path = NormalizedPath('././'); @@ -105,8 +94,7 @@ void main() { }); group('Interning', () { - test( - 'Given identical path strings, ' + test('Given identical path strings, ' 'when creating NormalizedPath, ' 'then returns identical instances', () { final path1 = NormalizedPath('/a/b'); @@ -114,8 +102,7 @@ void main() { expect(identical(path1, path2), isTrue); }); - test( - 'Given logically equivalent path strings, ' + test('Given logically equivalent path strings, ' 'when creating NormalizedPath, ' 'then returns identical instances', () { final path1 = NormalizedPath('a/b'); // No leading slash @@ -125,8 +112,7 @@ void main() { expect(identical(path1, path3), isTrue); }); - test( - 'Given different logical paths, ' + test('Given different logical paths, ' 'when creating NormalizedPath, ' 'then returns different instances', () { final path1 = NormalizedPath('/a/b'); @@ -142,8 +128,7 @@ void main() { }); group('Equality and HashCode', () { - test( - 'Given identical path strings, ' + test('Given identical path strings, ' 'when creating NormalizedPath, ' 'then instances are equal and hash codes match', () { final path1 = NormalizedPath('/a/b'); @@ -152,8 +137,7 @@ void main() { expect(path1.hashCode, equals(path2.hashCode)); }); - test( - 'Given logically equivalent path strings, ' + test('Given logically equivalent path strings, ' 'when creating NormalizedPath, ' 'then instances are equal and hash codes match', () { final path1 = NormalizedPath('a/b'); @@ -165,8 +149,7 @@ void main() { expect(path1.hashCode, equals(path3.hashCode)); }); - test( - 'Given different logical paths, ' + test('Given different logical paths, ' 'when creating NormalizedPath, ' 'then instances are not equal', () { final path1 = NormalizedPath('/a/b'); @@ -178,8 +161,7 @@ void main() { expect(path1, isNot(equals(path4))); }); - test( - 'Given different types, ' + test('Given different types, ' 'when comparing, ' 'then == returns false', () { final path = NormalizedPath('/a/b'); @@ -190,49 +172,37 @@ void main() { group('Given different instances, but same hashCode (collision), ', () { late final real = NormalizedPath('/a/b'); - test( - 'when segment count differs, ' - 'then == returns false via length check', - () { - // Force same hashCode but only one segment - final fake = _FakeNormalizedPath(NormalizedPath('/a'), real.hashCode); + test('when segment count differs, ' + 'then == returns false via length check', () { + // Force same hashCode but only one segment + final fake = _FakeNormalizedPath(NormalizedPath('/a'), real.hashCode); - // hashCode check passes, then length check kicks in and returns false - expect(real == fake, isFalse); // use == to avoid matcher smartness - }, - ); + // hashCode check passes, then length check kicks in and returns false + expect(real == fake, isFalse); // use == to avoid matcher smartness + }); - test( - 'when segments differs, ' - 'then == returns false via length check', - () { - // Force same hashCode but only one segment - final fake = - _FakeNormalizedPath(NormalizedPath('/a/x'), real.hashCode); + test('when segments differs, ' + 'then == returns false via length check', () { + // Force same hashCode but only one segment + final fake = _FakeNormalizedPath(NormalizedPath('/a/x'), real.hashCode); - // hashCode and length check passes, then segment check kicks in and returns false - expect(real == fake, isFalse); // use == to avoid matcher smartness - }, - ); + // hashCode and length check passes, then segment check kicks in and returns false + expect(real == fake, isFalse); // use == to avoid matcher smartness + }); - test( - 'when segment are equal, ' - 'then == returns true', - () { - // Force same hashCode but only one segment - final fake = - _FakeNormalizedPath(NormalizedPath('/a/b'), real.hashCode); + test('when segment are equal, ' + 'then == returns true', () { + // Force same hashCode but only one segment + final fake = _FakeNormalizedPath(NormalizedPath('/a/b'), real.hashCode); - // These are equal despite not being same instance - expect(real == fake, isTrue); // use == to avoid matcher smartness - }, - ); + // These are equal despite not being same instance + expect(real == fake, isTrue); // use == to avoid matcher smartness + }); }); }); group('toString()', () { - test( - 'Given various path initializations, ' + test('Given various path initializations, ' 'when calling toString(), ' 'then returns canonical path starting with /', () { expect(NormalizedPath('a/b').toString(), equals('/a/b')); diff --git a/test/router/path_trie_crud_test.dart b/test/router/path_trie_crud_test.dart index f5c9c1aa..d45371b9 100644 --- a/test/router/path_trie_crud_test.dart +++ b/test/router/path_trie_crud_test.dart @@ -11,8 +11,7 @@ void main() { }); group('addOrUpdate', () { - test( - 'Given an empty trie, ' + test('Given an empty trie, ' 'when a new path is added with addOrUpdate, ' 'then the path is added', () { final path = NormalizedPath('/users'); @@ -27,8 +26,7 @@ void main() { expect(result.parameters, isEmpty); }); - test( - 'Given a trie with an existing path, ' + test('Given a trie with an existing path, ' 'when addOrUpdate is called for the same path with a new value, ' 'then the value is updated', () { final path = NormalizedPath('/users'); @@ -44,8 +42,7 @@ void main() { expect(result!.value, equals(updatedValue)); }); - test( - 'Given an empty trie, ' + test('Given an empty trie, ' 'when a new parameterized path is added with addOrUpdate, ' 'then the path is added', () { final pathDefinition = NormalizedPath('/users/:id'); @@ -61,8 +58,7 @@ void main() { expect(result.parameters, equals({#id: '123'})); }); - test( - 'Given a trie with an existing parameterized path, ' + test('Given a trie with an existing parameterized path, ' 'when addOrUpdate is called for that path with a new value, ' 'then the value is updated', () { final pathDefinition = NormalizedPath('/users/:id'); @@ -80,8 +76,7 @@ void main() { expect(result.parameters, equals({#id: '123'})); }); - test( - 'Given a trie with a path /a (with a value), ' + test('Given a trie with a path /a (with a value), ' 'when addOrUpdate is called for /a/b, ' 'then both are retrievable', () { final pathA = NormalizedPath('/a'); @@ -96,24 +91,28 @@ void main() { }); test( - 'Given a trie with a path /a/b/c (making /a/b an intermediate node), ' - 'when addOrUpdate is called for /a/b to give it a value, ' - 'then both are retrievable', () { - final pathABC = NormalizedPath('/a/b/c'); - final pathAB = NormalizedPath('/a/b'); - trie.addOrUpdate(pathABC, 1); // /a/b is created as an intermediate node - - final wasAddedAB = trie.addOrUpdate(pathAB, 2); - - expect(wasAddedAB, isTrue); - expect(trie.lookup(pathAB)?.value, 2); - expect(trie.lookup(pathABC)?.value, 1); - }); + 'Given a trie with a path /a/b/c (making /a/b an intermediate node), ' + 'when addOrUpdate is called for /a/b to give it a value, ' + 'then both are retrievable', + () { + final pathABC = NormalizedPath('/a/b/c'); + final pathAB = NormalizedPath('/a/b'); + trie.addOrUpdate( + pathABC, + 1, + ); // /a/b is created as an intermediate node + + final wasAddedAB = trie.addOrUpdate(pathAB, 2); + + expect(wasAddedAB, isTrue); + expect(trie.lookup(pathAB)?.value, 2); + expect(trie.lookup(pathABC)?.value, 1); + }, + ); }); group('update', () { - test( - 'Given a trie with an existing path and value, ' + test('Given a trie with an existing path and value, ' 'when update is called with that path and a new value, ' 'then lookup returns the new value', () { final path = NormalizedPath('/posts'); @@ -126,33 +125,39 @@ void main() { expect(result!.value, equals(2)); }); - test( - 'Given a trie, ' + test('Given a trie, ' 'when update is called for a path that does not exist, ' 'then an ArgumentError is thrown', () { final path = NormalizedPath('/nonexistent'); expect(() => trie.update(path, 1), throwsArgumentError); - expect(trie.lookup(path), isNull, - reason: 'Path should not have been added.'); + expect( + trie.lookup(path), + isNull, + reason: 'Path should not have been added.', + ); }); - test( - 'Given a trie with /a/b (making /a intermediate and valueless), ' + test('Given a trie with /a/b (making /a intermediate and valueless), ' 'when update is called for /a, ' 'then an ArgumentError is thrown', () { trie.add(NormalizedPath('/a/b'), 2); final pathA = NormalizedPath('/a'); expect(() => trie.update(pathA, 1), throwsArgumentError); - expect(trie.lookup(pathA), isNull, - reason: '/a should not have gained a value.'); - expect(trie.lookup(NormalizedPath('/a/b'))?.value, 2, - reason: 'Child path should be unaffected.'); + expect( + trie.lookup(pathA), + isNull, + reason: '/a should not have gained a value.', + ); + expect( + trie.lookup(NormalizedPath('/a/b'))?.value, + 2, + reason: 'Child path should be unaffected.', + ); }); - test( - 'Given a trie with an existing parameterized path and value, ' + test('Given a trie with an existing parameterized path and value, ' 'when update is called with that path and a new value, ' 'then lookup returns the new value', () { final pathDefinition = NormalizedPath('/posts/:id'); @@ -166,8 +171,7 @@ void main() { expect(result.parameters, equals({#id: 'abc'})); }); - test( - 'Given a trie, ' + test('Given a trie, ' 'when update is called for a parameterized path that does not exist as a defined route, ' 'then an ArgumentError is thrown', () { final pathDefinition = NormalizedPath('/articles/:id'); @@ -176,8 +180,7 @@ void main() { expect(trie.lookup(NormalizedPath('/articles/any')), isNull); }); - test( - 'Given a trie with an existing wildcard path /data/* and value, ' + test('Given a trie with an existing wildcard path /data/* and value, ' 'when update is called for /data/* with a new value, ' 'then lookup for a matching path returns the new value', () { final pathDefinition = NormalizedPath('/data/*'); @@ -190,13 +193,14 @@ void main() { expect(result, isNotNull); expect(result!.value, equals(2)); expect( - result.parameters, isEmpty); // Wildcards don't produce parameters + result.parameters, + isEmpty, + ); // Wildcards don't produce parameters expect(result.matched.toString(), '/data/something'); expect(result.remaining.segments, isEmpty); }); - test( - 'Given a trie, ' + test('Given a trie, ' 'when update is called for a wildcard path /data/* that does not exist as a defined route, ' 'then an ArgumentError is thrown', () { final pathDefinition = NormalizedPath('/data/*'); @@ -204,8 +208,7 @@ void main() { expect(trie.lookup(NormalizedPath('/data/anything')), isNull); }); - test( - 'Given a trie with an existing tail path /files/** and value, ' + test('Given a trie with an existing tail path /files/** and value, ' 'when update is called for /files/** with a new value, ' 'then lookup for a matching path returns the new value', () { final pathDefinition = NormalizedPath('/files/**'); @@ -222,8 +225,7 @@ void main() { expect(result.remaining.toString(), '/a/b.txt'); }); - test( - 'Given a trie, ' + test('Given a trie, ' 'when update is called for a tail path /files/** that does not exist as a defined route, ' 'then an ArgumentError is thrown', () { final pathDefinition = NormalizedPath('/files/**'); @@ -233,8 +235,7 @@ void main() { }); group('remove', () { - test( - 'Given a trie with an existing leaf path and value, ' + test('Given a trie with an existing leaf path and value, ' 'when remove is called, ' 'then the value returned is removed', () { final path = NormalizedPath('/comments'); @@ -246,8 +247,7 @@ void main() { expect(trie.lookup(path), isNull); }); - test( - 'Given a trie, ' + test('Given a trie, ' 'when remove is called for a path that does not exist, ' 'then nothing is removed', () { final path = NormalizedPath('/comments/123'); @@ -260,8 +260,7 @@ void main() { expect(trie.lookup(NormalizedPath('/other'))?.value, 1); }); - test( - 'Given a trie with /a/b (making /a intermediate and valueless), ' + test('Given a trie with /a/b (making /a intermediate and valueless), ' 'when remove is called for /a, ' 'then nothing is removed', () { final pathA = NormalizedPath('/a'); @@ -276,26 +275,32 @@ void main() { }); test( - 'Given a trie with /parent (value) and /parent/child (value), ' - 'when remove is called for /parent, ' - 'then /parent value is removed, but /parent/child is still accessible', - () { - final parentPath = NormalizedPath('/articles'); - final childPath = NormalizedPath('/articles/details'); - trie.add(parentPath, 1); - trie.add(childPath, 2); - - final removedValue = trie.remove(parentPath); - - expect(removedValue, equals(1)); - expect(trie.lookup(parentPath), isNull, - reason: 'Value of /articles should be removed.'); - expect(trie.lookup(childPath)?.value, 2, - reason: 'Child /articles/details should still be accessible.'); - }); - - test( - 'Given a trie with a parameterized path and value, ' + 'Given a trie with /parent (value) and /parent/child (value), ' + 'when remove is called for /parent, ' + 'then /parent value is removed, but /parent/child is still accessible', + () { + final parentPath = NormalizedPath('/articles'); + final childPath = NormalizedPath('/articles/details'); + trie.add(parentPath, 1); + trie.add(childPath, 2); + + final removedValue = trie.remove(parentPath); + + expect(removedValue, equals(1)); + expect( + trie.lookup(parentPath), + isNull, + reason: 'Value of /articles should be removed.', + ); + expect( + trie.lookup(childPath)?.value, + 2, + reason: 'Child /articles/details should still be accessible.', + ); + }, + ); + + test('Given a trie with a parameterized path and value, ' 'when remove is called, ' 'then it returns the removed value', () { final pathDefinition = NormalizedPath('/users/:id/settings'); @@ -307,12 +312,14 @@ void main() { expect(removedValue, equals(1)); expect(trie.lookup(NormalizedPath('/users/123/settings')), isNull); expect(trie.lookup(NormalizedPath('/users/any/settings')), isNull); - expect(trie.lookup(NormalizedPath('/users/data'))?.value, 2, - reason: 'Sibling path should be unaffected.'); + expect( + trie.lookup(NormalizedPath('/users/data'))?.value, + 2, + reason: 'Sibling path should be unaffected.', + ); }); - test( - 'Given a trie with root path / (value) and child /a (value), ' + test('Given a trie with root path / (value) and child /a (value), ' 'when remove is called for /, ' 'then / value is removed, but /a is still accessible', () { final rootPath = NormalizedPath('/'); @@ -327,8 +334,7 @@ void main() { expect(trie.lookup(childPath)?.value, 2); }); - test( - 'Given a trie with only root path / having a value, ' + test('Given a trie with only root path / having a value, ' 'when remove is called for /, ' 'then / value is removed', () { final rootPath = NormalizedPath('/'); @@ -340,8 +346,7 @@ void main() { expect(trie.lookup(rootPath), isNull); }); - test( - 'Given a trie with /a/b/c and /a/b/d, ' + test('Given a trie with /a/b/c and /a/b/d, ' 'when /a/b/c is removed, ' 'then /a/b/d should still be accessible.', () { final pathABC = NormalizedPath('/a/b/c'); @@ -357,30 +362,38 @@ void main() { }); test( - 'Given a trie with an existing wildcard path /data/* and value, ' - 'when remove is called for /data/*, ' - 'then the value is removed and lookup for matching paths returns null', - () { - final pathDefinition = NormalizedPath('/data/*'); - trie.add(pathDefinition, 10); - trie.add(NormalizedPath('/data/fixed'), 20); // Sibling literal - - final removedValue = trie.remove(pathDefinition); - - expect(removedValue, equals(10)); - expect(trie.lookup(NormalizedPath('/data/any')), isNull, - reason: 'Wildcard path should be removed.'); - expect(trie.lookup(NormalizedPath('/data/fixed'))?.value, 20, - reason: 'Sibling literal path should be unaffected.'); - }); - - test( - 'Given a trie, ' + 'Given a trie with an existing wildcard path /data/* and value, ' + 'when remove is called for /data/*, ' + 'then the value is removed and lookup for matching paths returns null', + () { + final pathDefinition = NormalizedPath('/data/*'); + trie.add(pathDefinition, 10); + trie.add(NormalizedPath('/data/fixed'), 20); // Sibling literal + + final removedValue = trie.remove(pathDefinition); + + expect(removedValue, equals(10)); + expect( + trie.lookup(NormalizedPath('/data/any')), + isNull, + reason: 'Wildcard path should be removed.', + ); + expect( + trie.lookup(NormalizedPath('/data/fixed'))?.value, + 20, + reason: 'Sibling literal path should be unaffected.', + ); + }, + ); + + test('Given a trie, ' 'when remove is called for a wildcard path /data/* that does not exist as a defined route, ' 'then null is returned and no other paths are affected', () { final pathDefinition = NormalizedPath('/data/*'); trie.add( - NormalizedPath('/other/*'), 1); // Add a different wildcard path + NormalizedPath('/other/*'), + 1, + ); // Add a different wildcard path final removedValue = trie.remove(pathDefinition); @@ -389,28 +402,37 @@ void main() { }); test( - 'Given a trie with an existing tail path /files/** and value, ' - 'when remove is called for /files/**, ' - 'then the value is removed and lookup for matching paths returns null', - () { - final pathDefinition = NormalizedPath('/files/**'); - trie.add(pathDefinition, 30); - trie.add(NormalizedPath('/files/specific/file.txt'), - 40); // More specific child - - final removedValue = trie.remove(pathDefinition); - - expect(removedValue, equals(30)); - expect(trie.lookup(NormalizedPath('/files/a/b/c')), isNull, - reason: 'Tail path should be removed.'); - final specificLookup = - trie.lookup(NormalizedPath('/files/specific/file.txt')); - expect(specificLookup?.value, 40, - reason: 'More specific path should remain'); - }); - - test( - 'Given a trie, ' + 'Given a trie with an existing tail path /files/** and value, ' + 'when remove is called for /files/**, ' + 'then the value is removed and lookup for matching paths returns null', + () { + final pathDefinition = NormalizedPath('/files/**'); + trie.add(pathDefinition, 30); + trie.add( + NormalizedPath('/files/specific/file.txt'), + 40, + ); // More specific child + + final removedValue = trie.remove(pathDefinition); + + expect(removedValue, equals(30)); + expect( + trie.lookup(NormalizedPath('/files/a/b/c')), + isNull, + reason: 'Tail path should be removed.', + ); + final specificLookup = trie.lookup( + NormalizedPath('/files/specific/file.txt'), + ); + expect( + specificLookup?.value, + 40, + reason: 'More specific path should remain', + ); + }, + ); + + test('Given a trie, ' 'when remove is called for a tail path /files/** that does not exist as a defined route, ' 'then null is returned and no other paths are affected', () { final pathDefinition = NormalizedPath('/files/**'); diff --git a/test/router/path_trie_tail_test.dart b/test/router/path_trie_tail_test.dart index 97ebd220..20c8fb85 100644 --- a/test/router/path_trie_tail_test.dart +++ b/test/router/path_trie_tail_test.dart @@ -10,8 +10,7 @@ void main() { trie = PathTrie(); }); - test( - 'Given a trie with path /static/**, ' + test('Given a trie with path /static/**, ' 'when /static/css/style.css is looked up, ' 'then it matches with correct value and remaining path', () { trie.add(NormalizedPath('/static/**'), 1); @@ -24,18 +23,19 @@ void main() { }); test( - 'Given a trie with path /**, ' - 'when /any/path/anywhere is looked up, ' - 'then it matches with an empty matched path and correct remaining path', - () { - trie.add(NormalizedPath('/**'), 1); - final result = trie.lookup(NormalizedPath('/any/path/anywhere')); - expect(result, isNotNull); - expect(result!.value, 1); - expect(result.parameters, isEmpty); - expect(result.matched.segments, isEmpty); - expect(result.remaining.path, '/any/path/anywhere'); - }); + 'Given a trie with path /**, ' + 'when /any/path/anywhere is looked up, ' + 'then it matches with an empty matched path and correct remaining path', + () { + trie.add(NormalizedPath('/**'), 1); + final result = trie.lookup(NormalizedPath('/any/path/anywhere')); + expect(result, isNotNull); + expect(result!.value, 1); + expect(result.parameters, isEmpty); + expect(result.matched.segments, isEmpty); + expect(result.remaining.path, '/any/path/anywhere'); + }, + ); group('Root path (/) matching with root tail (/**) interactions', () { setUp(() { @@ -44,69 +44,88 @@ void main() { }); test( - 'Given a trie with only path /** defined, ' - 'when the root path / is looked up,' - 'then /** matches with its value and empty matched/remaining paths', - () { - trie.add(NormalizedPath('/**'), 1); - final result = trie.lookup(NormalizedPath('/')); - expect(result, isNotNull, - reason: 'Lookup for / should find /** if / has no value'); - expect(result!.value, 1); - expect(result.matched.segments, isEmpty, - reason: 'Matched path for /** lookup of / should be empty'); - expect(result.remaining.segments, isEmpty, - reason: 'Remaining path for /** lookup of / should be empty'); - }); - - test( - 'Given a trie with only path / defined, ' - 'when the root path / is looked up, ' - 'then / matches with its value and empty matched/remaining paths', - () { - trie.add(NormalizedPath('/'), 2); - final result = trie.lookup(NormalizedPath('/')); - expect(result, isNotNull); - expect(result!.value, 2); - expect(result.matched.segments, isEmpty); - expect(result.remaining.segments, isEmpty); - }); + 'Given a trie with only path /** defined, ' + 'when the root path / is looked up,' + 'then /** matches with its value and empty matched/remaining paths', + () { + trie.add(NormalizedPath('/**'), 1); + final result = trie.lookup(NormalizedPath('/')); + expect( + result, + isNotNull, + reason: 'Lookup for / should find /** if / has no value', + ); + expect(result!.value, 1); + expect( + result.matched.segments, + isEmpty, + reason: 'Matched path for /** lookup of / should be empty', + ); + expect( + result.remaining.segments, + isEmpty, + reason: 'Remaining path for /** lookup of / should be empty', + ); + }, + ); test( - 'Given a trie with path / and path /** defined, ' + 'Given a trie with only path / defined, ' + 'when the root path / is looked up, ' + 'then / matches with its value and empty matched/remaining paths', + () { + trie.add(NormalizedPath('/'), 2); + final result = trie.lookup(NormalizedPath('/')); + expect(result, isNotNull); + expect(result!.value, 2); + expect(result.matched.segments, isEmpty); + expect(result.remaining.segments, isEmpty); + }, + ); + + test('Given a trie with path / and path /** defined, ' 'when the root path / is looked up, ' 'then the literal path / takes precedence with its value', () { trie.add(NormalizedPath('/'), 2); trie.add(NormalizedPath('/**'), 3); // /** has a different value final result = trie.lookup(NormalizedPath('/')); - expect(result, isNotNull, - reason: 'Lookup for / should prefer value on / over /**'); + expect( + result, + isNotNull, + reason: 'Lookup for / should prefer value on / over /**', + ); expect(result!.value, 2, reason: 'Value from / should be preferred'); expect(result.matched.segments, isEmpty); expect(result.remaining.segments, isEmpty); }); test( - 'Given a trie with path / and path /** defined, ' - 'when a sub-path like /some/path is looked up, ' - 'then path /** matches with its value and correct remaining path', - () { - trie.add(NormalizedPath('/'), 2); - trie.add(NormalizedPath('/**'), 3); - - final result = trie.lookup(NormalizedPath('/some/path')); - expect(result, isNotNull, - reason: '/** should still match longer paths'); - expect(result!.value, 3, - reason: 'Value from /** should match longer paths'); - expect(result.matched.segments, isEmpty); - expect(result.remaining.path, '/some/path'); - }); + 'Given a trie with path / and path /** defined, ' + 'when a sub-path like /some/path is looked up, ' + 'then path /** matches with its value and correct remaining path', + () { + trie.add(NormalizedPath('/'), 2); + trie.add(NormalizedPath('/**'), 3); + + final result = trie.lookup(NormalizedPath('/some/path')); + expect( + result, + isNotNull, + reason: '/** should still match longer paths', + ); + expect( + result!.value, + 3, + reason: 'Value from /** should match longer paths', + ); + expect(result.matched.segments, isEmpty); + expect(result.remaining.path, '/some/path'); + }, + ); }); - test( - 'Given a trie with /assets/js/app.js and /assets/**, ' + test('Given a trie with /assets/js/app.js and /assets/**, ' 'when paths are looked up, ' 'then literal is preferred and tail matches remaining', () { trie.add(NormalizedPath('/assets/js/app.js'), 1); @@ -125,8 +144,7 @@ void main() { expect(tailResult.remaining.path, '/img/logo.png'); }); - test( - 'Given a trie with /foo/bar/** and /foo/**, ' + test('Given a trie with /foo/bar/** and /foo/**, ' 'when paths are looked up, ' 'then the more specific /foo/bar/** is chosen over /foo/**', () { trie.add(NormalizedPath('/foo/bar/**'), 1); @@ -145,13 +163,13 @@ void main() { expect(resGeneral.remaining.path, '/other/path'); }); - test( - 'Given a trie with path /user/:id/files/**, ' + test('Given a trie with path /user/:id/files/**, ' 'when /user/42/files/docs/report.pdf is looked up, ' 'then it matches with correct parameter and remaining path', () { trie.add(NormalizedPath('/user/:id/files/**'), 1); - final result = - trie.lookup(NormalizedPath('/user/42/files/docs/report.pdf')); + final result = trie.lookup( + NormalizedPath('/user/42/files/docs/report.pdf'), + ); expect(result, isNotNull); expect(result!.value, 1); expect(result.parameters, equals({#id: '42'})); @@ -159,71 +177,76 @@ void main() { expect(result.remaining.path, '/docs/report.pdf'); }); - test( - 'Given a trie with path /data/export/**, ' + test('Given a trie with path /data/export/**, ' 'when /data (shorter than prefix) is looked up, ' 'then no match is found', () { trie.add(NormalizedPath('/data/export/**'), 1); expect(trie.lookup(NormalizedPath('/data')), isNull); }); - test( - 'Given an empty trie, ' + test('Given an empty trie, ' 'when adding a path like /**foo, ' 'then an ArgumentError is thrown', () { - expect(() => trie.add(NormalizedPath('/downloads/**foo'), 1), - throwsArgumentError, - reason: 'Tail not a full segment'); + expect( + () => trie.add(NormalizedPath('/downloads/**foo'), 1), + throwsArgumentError, + reason: 'Tail not a full segment', + ); }); group('Tail and Other Segment interaction validation', () { - test( - 'Given a trie with /test/**, ' + test('Given a trie with /test/**, ' 'when adding /test/:id, ' 'then an ArgumentError is thrown', () { trie.add(NormalizedPath('/test/**'), 1); expect( - () => trie.add(NormalizedPath('/test/:id'), 2), throwsArgumentError, - reason: 'Parameter after tail at same level'); + () => trie.add(NormalizedPath('/test/:id'), 2), + throwsArgumentError, + reason: 'Parameter after tail at same level', + ); }); - test( - 'Given a trie with /test/:id, ' + test('Given a trie with /test/:id, ' 'when adding /test/**, ' 'then an ArgumentError is thrown', () { trie.add(NormalizedPath('/test/:id'), 1); expect( - () => trie.add(NormalizedPath('/test/**'), 2), throwsArgumentError, - reason: 'Tail after parameter at same level'); + () => trie.add(NormalizedPath('/test/**'), 2), + throwsArgumentError, + reason: 'Tail after parameter at same level', + ); }); - test( - 'Given a trie with /test/**, ' + test('Given a trie with /test/**, ' 'when adding /test/*, ' 'then an ArgumentError is thrown', () { trie.add(NormalizedPath('/test/**'), 1); expect( - () => trie.add(NormalizedPath('/test/*'), 2), throwsArgumentError, - reason: 'Wildcard after tail at same level'); + () => trie.add(NormalizedPath('/test/*'), 2), + throwsArgumentError, + reason: 'Wildcard after tail at same level', + ); }); - test( - 'Given a trie with /test/*, ' + test('Given a trie with /test/*, ' 'when adding /test/**, ' 'then an ArgumentError is thrown', () { trie.add(NormalizedPath('/test/*'), 1); expect( - () => trie.add(NormalizedPath('/test/**'), 2), throwsArgumentError, - reason: 'Tail after wildcard at same level'); + () => trie.add(NormalizedPath('/test/**'), 2), + throwsArgumentError, + reason: 'Tail after wildcard at same level', + ); }); - test( - 'Given an empty trie, ' + test('Given an empty trie, ' 'when adding a path /a/**/b/c (tail /** not as the last segment), ' 'then an ArgumentError is thrown', () { expect( - () => trie.add(NormalizedPath('/a/**/b/c'), 1), throwsArgumentError, - reason: 'Tail /** not as the last segment.'); + () => trie.add(NormalizedPath('/a/**/b/c'), 1), + throwsArgumentError, + reason: 'Tail /** not as the last segment.', + ); }); }); }); diff --git a/test/router/path_trie_test.dart b/test/router/path_trie_test.dart index 8ae3cd0e..24ac8bf5 100644 --- a/test/router/path_trie_test.dart +++ b/test/router/path_trie_test.dart @@ -11,8 +11,7 @@ void main() { }); group('Adding and Looking Up Basic Routes', () { - test( - 'Given a simple literal path, ' + test('Given a simple literal path, ' 'when added and looked up, ' 'then returns correct value and empty parameters', () { trie.add(NormalizedPath('/users'), 1); @@ -22,24 +21,21 @@ void main() { expect(result.parameters, isEmpty); }); - test( - 'Given a path not added to the trie, ' + test('Given a path not added to the trie, ' 'when looked up, ' 'then returns null', () { trie.add(NormalizedPath('/users'), 1); expect(trie.lookup(NormalizedPath('/posts')), isNull); }); - test( - 'Given a path that is only a prefix of an added route, ' + test('Given a path that is only a prefix of an added route, ' 'when looked up, ' 'then returns null', () { trie.add(NormalizedPath('/users/profile'), 1); expect(trie.lookup(NormalizedPath('/users')), isNull); }); - test( - 'Given a path added to the trie, ' + test('Given a path added to the trie, ' 'when a non-matching path segment is looked up, ' 'then returns null', () { trie.add(NormalizedPath('/users/profile/settings'), 1); @@ -48,8 +44,7 @@ void main() { }); group('Parameter Handling', () { - test( - 'Given a path with one parameter, ' + test('Given a path with one parameter, ' 'when added and looked up with a matching path, ' 'then returns correct value and extracted parameter', () { trie.add(NormalizedPath('/users/:id'), 2); @@ -59,8 +54,7 @@ void main() { expect(result.parameters, equals({#id: '123'})); }); - test( - 'Given a path with multiple parameters, ' + test('Given a path with multiple parameters, ' 'when added and looked up with a matching path, ' 'then returns correct value and all extracted parameters', () { trie.add(NormalizedPath('/users/:userId/posts/:postId'), 3); @@ -70,8 +64,7 @@ void main() { expect(result.parameters, equals({#userId: 'abc', #postId: 'xyz'})); }); - test( - 'Given paths with parameters at different levels, ' + test('Given paths with parameters at different levels, ' 'when looked up, ' 'then matches correctly and extracts parameters', () { trie.add(NormalizedPath('/:entity/:id'), 1); @@ -88,8 +81,7 @@ void main() { expect(result.parameters, equals({#id: '789'})); }); - test( - 'Given a path with repeated parameters at different levels, ' + test('Given a path with repeated parameters at different levels, ' 'when looked up, ' 'then last extracted parameter wins', () { trie.add(NormalizedPath('/:id/:id'), 1); @@ -102,8 +94,7 @@ void main() { }); group('Route Precedence', () { - test( - 'Given both a literal and parameterized route at the same level, ' + test('Given both a literal and parameterized route at the same level, ' 'when looking up paths, ' 'then literal segments are prioritized over parameters', () { trie.add(NormalizedPath('/users/:id'), 1); // Parameter @@ -131,17 +122,19 @@ void main() { trie.add(NormalizedPath('/users/:id/profile'), 2); final result = trie.lookup(NormalizedPath('/users/789')); - expect(result, isNull, - reason: - 'Should not match the first route, as literal match has priority at each level. ' - 'Should not match the second route, as /profile segment is missing.'); + expect( + result, + isNull, + reason: + 'Should not match the first route, as literal match has priority at each level. ' + 'Should not match the second route, as /profile segment is missing.', + ); }, ); }); group('Error Handling', () { - test( - 'Given a literal route already exists, ' + test('Given a literal route already exists, ' 'when adding the same literal route again, ' 'then throws ArgumentError', () { const path = '/path'; @@ -167,8 +160,7 @@ void main() { ); }); - test( - 'Given a parameterized route already exists, ' + test('Given a parameterized route already exists, ' 'when adding the same parameterized route again, ' 'then throws ArgumentError', () { const path = '/path/:id'; @@ -196,8 +188,7 @@ void main() { ); }); - test( - 'Given a parameterized route exists, ' + test('Given a parameterized route exists, ' 'when adding another route with a conflicting parameter name at the same level, ' 'then throws ArgumentError', () { // Add initial route @@ -222,8 +213,7 @@ void main() { ); }); - test( - 'Given a normalizedPath with an unnamed parameter, ' + test('Given a normalizedPath with an unnamed parameter, ' 'when trying to add to a trie, ' 'then it fails ', () { expect(() => trie.add(NormalizedPath('/:'), 1), throwsArgumentError); @@ -231,8 +221,7 @@ void main() { }); group('Edge Cases', () { - test( - 'Given the root path, ' + test('Given the root path, ' 'when added and looked up, ' 'then returns correct value and empty parameters', () { trie.add(NormalizedPath('/'), 1); @@ -242,8 +231,7 @@ void main() { expect(result.parameters, isEmpty); }); - test( - 'Given the root path and another path, ' + test('Given the root path and another path, ' 'when looked up, ' 'then correctly distinguishes between them', () { trie.add(NormalizedPath('/'), 1); @@ -256,8 +244,7 @@ void main() { expect(result!.value, equals(2)); }); - test( - 'Given paths with trailing slashes, ' + test('Given paths with trailing slashes, ' 'when added and looked up (using NormalizedPath), ' 'then behaves consistently as if slashes were removed', () { // NormalizedPath removes trailing slashes (except for '/') @@ -275,13 +262,14 @@ void main() { }); group('Complex Scenarios', () { - test( - 'Given a mix of literal and parameterized routes at various depths, ' + test('Given a mix of literal and parameterized routes at various depths, ' 'when looking up different matching and non-matching paths, ' 'then returns correct values/parameters or null appropriately', () { trie.add(NormalizedPath('/api/v1/users/:userId/data'), 1); trie.add( - NormalizedPath('/api/v1/users/:userId/settings/:settingId'), 2); + NormalizedPath('/api/v1/users/:userId/settings/:settingId'), + 2, + ); trie.add(NormalizedPath('/api/v1/posts/:postId'), 3); trie.add( NormalizedPath('/api/v1/posts/latest'), @@ -340,8 +328,7 @@ void main() { trieB = PathTrie(); }); - test( - 'Given two tries, one with a route, ' + test('Given two tries, one with a route, ' 'when the second trie is attached to the first, ' 'then routes from the second trie are accessible via the first', () { // Setup trieB @@ -357,18 +344,13 @@ void main() { expect(result.parameters, isEmpty); }); - test( - 'Given an empty trie, ' + test('Given an empty trie, ' 'when attempting to attach another trie at the root path ("/"), ' 'then it succeeds', () { - expect( - () => trieA.attach(NormalizedPath('/'), trieB), - returnsNormally, - ); + expect(() => trieA.attach(NormalizedPath('/'), trieB), returnsNormally); }); - test( - 'Given a trie with an existing path, ' + test('Given a trie with an existing path, ' 'when attempting to attach another trie on a sub-path, ' 'then it succeeds and both tries are updated', () { trieA.add(NormalizedPath('/pathA/existing/A'), 1); @@ -390,47 +372,58 @@ void main() { expect(result!.value, equals(1)); }); - test( - 'Given a trie with an existing path, ' + test('Given a trie with an existing path, ' 'when attempting to attach another trie at that same path that, ' 'then it fails', () { trieA.add(NormalizedPath('/existing'), 1); trieB.add(NormalizedPath('/'), 2); expect( () => trieA.attach(NormalizedPath('/existing'), trieB), - throwsA(isA().having( - (final e) => e.message, 'message', equals('Conflicting values'))), + throwsA( + isA().having( + (final e) => e.message, + 'message', + equals('Conflicting values'), + ), + ), ); }); - test( - 'Given a trie with an existing parameterized path, ' + test('Given a trie with an existing parameterized path, ' 'when attempting to attach another trie with a parameterized root path at that level' 'then it fails due to conflicting parameters', () { trieA.add(NormalizedPath('/existing/:a'), 1); trieB.add(NormalizedPath('/:b'), 2); expect( () => trieA.attach(NormalizedPath('/existing'), trieB), - throwsA(isA().having((final e) => e.message, 'message', - equals('Conflicting parameters'))), + throwsA( + isA().having( + (final e) => e.message, + 'message', + equals('Conflicting parameters'), + ), + ), ); }); - test( - 'Given a trie with an existing path, ' + test('Given a trie with an existing path, ' 'when attempting to attach another trie such that children names would overlap, ' 'then it fails due to conflicting children', () { trieA.add(NormalizedPath('/existing/foo'), 1); trieB.add(NormalizedPath('/foo'), 2); expect( () => trieA.attach(NormalizedPath('/existing'), trieB), - throwsA(isA().having((final e) => e.message, 'message', - equals('Conflicting children'))), + throwsA( + isA().having( + (final e) => e.message, + 'message', + equals('Conflicting children'), + ), + ), ); }); - test( - 'Given trie B attached to trie A, ' + test('Given trie B attached to trie A, ' 'when trie B is updated with a new route after attachment, ' 'then the new route is accessible via trie A', () { // Initial setup and attachment @@ -446,8 +439,7 @@ void main() { expect(result!.value, equals(20)); }); - test( - 'Given trie B with parameterized routes attached to trie A, ' + test('Given trie B with parameterized routes attached to trie A, ' 'when looking up paths in trie A that extend into trie B, ' 'then parameters from trieB are correctly resolved', () { // Setup trieB with a parameterized route @@ -462,45 +454,47 @@ void main() { }); test( - 'Given trie B with parameterized routes attached to trie A under a parameterized route, ' - 'when looking up paths in trie A that extend into trie B, ' - 'then parameters from both tries are correctly resolved', () { - // More complex scenario: trieA has parameters, trieB is attached under that - trieB.add(NormalizedPath('/:paramB/endpoint'), 300); - trieA.add(NormalizedPath('/users/:userId'), 200); // A route in trieA - trieA.attach(NormalizedPath('/users/:userId/data'), trieB); - - // Lookup path spanning trieA (with param) and trieB (with param) - final result = - trieA.lookup(NormalizedPath('/users/user123/data/val456/endpoint')); - expect( - result, - isNotNull, - reason: 'Should find path through attached trieB', - ); - expect(result!.value, equals(300)); - expect( - result.parameters, - equals({ - #userId: 'user123', - #paramB: 'val456', - }), - ); - }); + 'Given trie B with parameterized routes attached to trie A under a parameterized route, ' + 'when looking up paths in trie A that extend into trie B, ' + 'then parameters from both tries are correctly resolved', + () { + // More complex scenario: trieA has parameters, trieB is attached under that + trieB.add(NormalizedPath('/:paramB/endpoint'), 300); + trieA.add(NormalizedPath('/users/:userId'), 200); // A route in trieA + trieA.attach(NormalizedPath('/users/:userId/data'), trieB); + + // Lookup path spanning trieA (with param) and trieB (with param) + final result = trieA.lookup( + NormalizedPath('/users/user123/data/val456/endpoint'), + ); + expect( + result, + isNotNull, + reason: 'Should find path through attached trieB', + ); + expect(result!.value, equals(300)); + expect( + result.parameters, + equals({#userId: 'user123', #paramB: 'val456'}), + ); + }, + ); test( - 'Given a path with repeated parameters at different levels introduced by attach, ' - 'when looked up, ' - 'then last extracted parameter wins', () { - trieA.add(NormalizedPath('/:id/'), 1); - trieB.add(NormalizedPath('/:id/'), 2); - trieA.attach(NormalizedPath('/:id/'), trieB); - - final result = trieA.lookup(NormalizedPath('/123/456')); - expect(result, isNotNull); - expect(result!.value, equals(2)); - expect(result.parameters, equals({#id: '456'})); - }); + 'Given a path with repeated parameters at different levels introduced by attach, ' + 'when looked up, ' + 'then last extracted parameter wins', + () { + trieA.add(NormalizedPath('/:id/'), 1); + trieB.add(NormalizedPath('/:id/'), 2); + trieA.attach(NormalizedPath('/:id/'), trieB); + + final result = trieA.lookup(NormalizedPath('/123/456')); + expect(result, isNotNull); + expect(result!.value, equals(2)); + expect(result.parameters, equals({#id: '456'})); + }, + ); }); }); } diff --git a/test/router/path_trie_use_test.dart b/test/router/path_trie_use_test.dart index 86404e81..7e3e3f36 100644 --- a/test/router/path_trie_use_test.dart +++ b/test/router/path_trie_use_test.dart @@ -3,8 +3,7 @@ import 'package:relic/src/router/path_trie.dart'; import 'package:test/test.dart'; void main() { - test( - 'Given a value at root, ' + test('Given a value at root, ' 'when use is applied to root, ' 'then the value is transformed', () { final trie = PathTrie(); @@ -14,8 +13,7 @@ void main() { expect(trie.lookup(root)?.value, 2, reason: 'Should double'); }); - test( - 'Given a value at path, ' + test('Given a value at path, ' 'when use is applied twice to path, ' 'then the mappings compose', () { final trie = PathTrie(); @@ -26,8 +24,7 @@ void main() { expect(trie.lookup(root)?.value, 8, reason: 'Should add 3 and then double'); }); - test( - 'Given an empty trie, ' + test('Given an empty trie, ' 'when use is applied before add, ' 'then the value is transformed', () { final trie = PathTrie(); @@ -37,42 +34,47 @@ void main() { expect(trie.lookup(root)?.value, 2, reason: 'Should double'); }); - test( - 'Given a value at tail wildcard, ' + test('Given a value at tail wildcard, ' 'when use is applied to tail wildcard, ' 'then matching paths are transformed', () { final trie = PathTrie(); final tail = NormalizedPath('/**'); trie.add(tail, 1); trie.use(tail, (final i) => i * 2); - expect(trie.lookup(NormalizedPath('/a/b/c'))?.value, 2, - reason: 'Should double'); + expect( + trie.lookup(NormalizedPath('/a/b/c'))?.value, + 2, + reason: 'Should double', + ); }); - test( - 'Given a value at parameterized path, ' + test('Given a value at parameterized path, ' 'when use is applied to parameter prefix, ' 'then descendant values are transformed', () { final trie = PathTrie(); trie.add(NormalizedPath('/:id/b/c'), 1); trie.use(NormalizedPath('/:id'), (final i) => i * 2); - expect(trie.lookup(NormalizedPath('/a/b/c'))?.value, 2, - reason: 'Should double'); + expect( + trie.lookup(NormalizedPath('/a/b/c'))?.value, + 2, + reason: 'Should double', + ); }); - test( - 'Given a value at wildcard path, ' + test('Given a value at wildcard path, ' 'when use is applied to wildcard prefix, ' 'then descendant values are transformed', () { final trie = PathTrie(); trie.add(NormalizedPath('/*/b/c'), 1); trie.use(NormalizedPath('/*'), (final i) => i * 2); - expect(trie.lookup(NormalizedPath('/a/b/c'))?.value, 2, - reason: 'Should double'); + expect( + trie.lookup(NormalizedPath('/a/b/c'))?.value, + 2, + reason: 'Should double', + ); }); - test( - 'Given multiple paths with values, ' + test('Given multiple paths with values, ' 'when use is applied to root, ' 'then all descendant values are transformed', () { final trie = PathTrie(); @@ -88,8 +90,7 @@ void main() { expect(trie.lookup(pathB)?.value, 200, reason: 'Should double'); }); - test( - 'Given multiple paths with values, ' + test('Given multiple paths with values, ' 'when use is applied to a specific path, ' 'then only descendants of that path are transformed', () { final trie = PathTrie(); @@ -105,8 +106,7 @@ void main() { expect(trie.lookup(pathB)?.value, 100, reason: 'Should not change'); }); - test( - 'Given two tries where one is attached to the other, ' + test('Given two tries where one is attached to the other, ' 'when use is applied to the prefix on the parent, ' 'then only attached trie values are transformed', () { final trieA = PathTrie(); @@ -125,8 +125,7 @@ void main() { ); }); - test( - 'Given two tries where one is attached to the other, ' + test('Given two tries where one is attached to the other, ' 'when use is applied to the root of the child, ' 'then only attached trie values are transformed', () { final trieA = PathTrie(); @@ -145,8 +144,7 @@ void main() { ); }); - test( - 'Given two tries with use applied, ' + test('Given two tries with use applied, ' 'when attaching one to the other such that use collide, ' 'then map functions are composed', () { final trieA = PathTrie(); @@ -156,12 +154,14 @@ void main() { trieB.add(NormalizedPath('/suffix'), 1); trieB.use(NormalizedPath.empty, (final i) => i + 3); trieA.attach(attachAt, trieB); - expect(trieA.lookup(NormalizedPath('/prefix/suffix'))?.value, 8, - reason: 'Should add 3 and then double'); + expect( + trieA.lookup(NormalizedPath('/prefix/suffix'))?.value, + 8, + reason: 'Should add 3 and then double', + ); }); - test( - 'Given multiple use mappings on ancestor and descendant paths, ' + test('Given multiple use mappings on ancestor and descendant paths, ' 'when looking up a value, ' 'then transformations are applied from leaf to root', () { final trie = PathTrie(); @@ -170,12 +170,14 @@ void main() { trie.add(pathB, 1); trie.use(pathA, (final i) => i * 2); trie.use(pathB, (final i) => i + 3); - expect(trie.lookup(pathB)?.value, 8, - reason: 'Should add 3 and then double'); + expect( + trie.lookup(pathB)?.value, + 8, + reason: 'Should add 3 and then double', + ); }); - test( - 'Given a trie of functions with hierarchical use mappings, ' + test('Given a trie of functions with hierarchical use mappings, ' 'when looking up the leaf function and applying it, ' 'then the call order is root to leaf', () { final trie = PathTrie(); diff --git a/test/router/path_trie_wildcard_test.dart b/test/router/path_trie_wildcard_test.dart index bd819b7e..5f53aeef 100644 --- a/test/router/path_trie_wildcard_test.dart +++ b/test/router/path_trie_wildcard_test.dart @@ -10,8 +10,7 @@ void main() { trie = PathTrie(); }); - test( - 'Given a trie with path /users/*/profile, ' + test('Given a trie with path /users/*/profile, ' 'when /users/123/profile is looked up, ' 'then it matches with correct value and paths', () { trie.add(NormalizedPath('/users/*/profile'), 1); @@ -23,8 +22,7 @@ void main() { expect(result.remaining.segments, isEmpty); }); - test( - 'Given a trie with path /*/resource, ' + test('Given a trie with path /*/resource, ' 'when /any/resource is looked up, ' 'then it matches with correct value and paths', () { trie.add(NormalizedPath('/*/resource'), 1); @@ -36,8 +34,7 @@ void main() { expect(result.remaining.segments, isEmpty); }); - test( - 'Given a trie with path /files/*, ' + test('Given a trie with path /files/*, ' 'when /files/image.jpg is looked up, ' 'then it matches with correct value and paths', () { trie.add(NormalizedPath('/files/*'), 1); @@ -49,8 +46,7 @@ void main() { expect(result.remaining.segments, isEmpty); }); - test( - 'Given a trie with path /a/*/c/*, ' + test('Given a trie with path /a/*/c/*, ' 'when /a/b/c/d is looked up, ' 'then it matches with correct value and paths', () { trie.add(NormalizedPath('/a/*/c/*'), 1); @@ -62,8 +58,7 @@ void main() { expect(result.remaining.segments, isEmpty); }); - test( - 'Given a trie with path /a/*/b, ' + test('Given a trie with path /a/*/b, ' 'when /a/b (fewer segments) is looked up, ' 'then no match is found', () { trie.add(NormalizedPath('/a/*/b'), 1); @@ -71,31 +66,30 @@ void main() { }); test( - 'Given a trie with /data/specific and /data/*, ' - 'when they are looked up, ' - 'then literal /data/specific is preferred over /data/*, and /data/* matches other segments', - () { - trie.add(NormalizedPath('/data/specific'), 1); - trie.add(NormalizedPath('/data/*'), 2); - final result = trie.lookup(NormalizedPath('/data/specific')); - expect(result, isNotNull); - expect(result!.value, 1); - - final wildResult = trie.lookup(NormalizedPath('/data/general')); - expect(wildResult, isNotNull); - expect(wildResult!.value, 2); - }); - - test( - 'Given a trie with path /assets/*, ' + 'Given a trie with /data/specific and /data/*, ' + 'when they are looked up, ' + 'then literal /data/specific is preferred over /data/*, and /data/* matches other segments', + () { + trie.add(NormalizedPath('/data/specific'), 1); + trie.add(NormalizedPath('/data/*'), 2); + final result = trie.lookup(NormalizedPath('/data/specific')); + expect(result, isNotNull); + expect(result!.value, 1); + + final wildResult = trie.lookup(NormalizedPath('/data/general')); + expect(wildResult, isNotNull); + expect(wildResult!.value, 2); + }, + ); + + test('Given a trie with path /assets/*, ' 'when /assets/img/logo.png is looked up, ' 'then no match is found', () { trie.add(NormalizedPath('/assets/*'), 1); expect(trie.lookup(NormalizedPath('/assets/img/logo.png')), isNull); }); - test( - 'Given a trie with path /api/:version/data/*, ' + test('Given a trie with path /api/:version/data/*, ' 'when /api/v1/data/users is looked up, ' 'then it matches with correct value, parameter, and paths', () { trie.add(NormalizedPath('/api/:version/data/*'), 1); @@ -107,50 +101,51 @@ void main() { expect(result.remaining.segments, isEmpty); }); - test( - 'Given a trie with path /a/b/*/d, ' + test('Given a trie with path /a/b/*/d, ' 'when /a/b/c (shorter) is looked up, ' 'then no match is found', () { trie.add(NormalizedPath('/a/b/*/d'), 1); expect(trie.lookup(NormalizedPath('/a/b/c')), isNull); }); - test( - 'Given a trie with path /a/b/* (no tail), ' + test('Given a trie with path /a/b/* (no tail), ' 'when /a/b/c/d (longer) is looked up, ' 'then no match is found', () { trie.add(NormalizedPath('/a/b/*'), 1); expect(trie.lookup(NormalizedPath('/a/b/c/d')), isNull); }); - test( - 'Given an empty trie, ' + test('Given an empty trie, ' 'when adding a path like /*foo/bar, ' 'then an ArgumentError is thrown', () { expect( - () => trie.add(NormalizedPath('/*foo/bar'), 1), throwsArgumentError, - reason: 'Wildcard not a full segment'); + () => trie.add(NormalizedPath('/*foo/bar'), 1), + throwsArgumentError, + reason: 'Wildcard not a full segment', + ); }); group('Wildcard and Parameter interaction validation', () { - test( - 'Given a trie with /test/*, ' + test('Given a trie with /test/*, ' 'when adding /test/:id, ' 'then an ArgumentError is thrown', () { trie.add(NormalizedPath('/test/*'), 1); expect( - () => trie.add(NormalizedPath('/test/:id'), 2), throwsArgumentError, - reason: 'Parameter after wildcard at same level'); + () => trie.add(NormalizedPath('/test/:id'), 2), + throwsArgumentError, + reason: 'Parameter after wildcard at same level', + ); }); - test( - 'Given a trie with /test/:id, ' + test('Given a trie with /test/:id, ' 'when adding /test/*, ' 'then an ArgumentError is thrown', () { trie.add(NormalizedPath('/test/:id'), 1); expect( - () => trie.add(NormalizedPath('/test/*'), 2), throwsArgumentError, - reason: 'Wildcard after parameter at same level'); + () => trie.add(NormalizedPath('/test/*'), 2), + throwsArgumentError, + reason: 'Wildcard after parameter at same level', + ); }); }); }); diff --git a/test/router/relic_app_test.dart b/test/router/relic_app_test.dart index 767efabf..b2641f4b 100644 --- a/test/router/relic_app_test.dart +++ b/test/router/relic_app_test.dart @@ -13,20 +13,18 @@ import 'package:vm_service/vm_service_io.dart' as vmi; void main() { group('RelicApp', () { - test( - 'Given RelicApp, ' + test('Given RelicApp, ' 'when it is instantiated, ' 'then it is a Router', () { final app = RelicApp(); expect(app, isA>()); }); - test( - 'Given a RelicApp, ' + test('Given a RelicApp, ' 'when calling run with adapter factory, ' 'then it creates a RelicServer and mounts the handler', () async { - final app = RelicApp() - ..any('/', (final ctx) => ctx.respond(Response.ok())); + final app = + RelicApp()..any('/', (final ctx) => ctx.respond(Response.ok())); final server = await app.run(() => _FakeAdapter()); @@ -35,12 +33,11 @@ void main() { await app.close(); }); - test( - 'Given a RelicApp, ' + test('Given a RelicApp, ' 'when calling serve, ' 'then it creates a RelicServer and mounts the handler', () async { - final app = RelicApp() - ..any('/', (final ctx) => ctx.respond(Response.ok())); + final app = + RelicApp()..any('/', (final ctx) => ctx.respond(Response.ok())); final server = await app.serve(port: 0); @@ -49,14 +46,14 @@ void main() { await app.close(); }); - test( - 'Given a RelicApp, ' + test('Given a RelicApp, ' 'when hot-reloading isolate, ' 'then it is rebuild', () async { final wsUri = (await Service.getInfo()).serverWebSocketUri; if (wsUri == null) { markTestSkipped( - 'VM service not available! Use: dart run --enable-vm-service'); + 'VM service not available! Use: dart run --enable-vm-service', + ); return; } if (Platform.script.path.endsWith('.dill')) { diff --git a/test/router/router_groups_test.dart b/test/router/router_groups_test.dart index 6e3157dc..04104df7 100644 --- a/test/router/router_groups_test.dart +++ b/test/router/router_groups_test.dart @@ -63,8 +63,7 @@ void main() { }); }); - test( - 'Given a router with a parameterized group and registered path, ' + test('Given a router with a parameterized group and registered path, ' 'when looking up a route with parameter values, ' 'then it returns the correct value with extracted parameters', () { final router = Router(); @@ -99,8 +98,7 @@ void main() { }); }); - test( - 'Given nested parameterized groups with a registered path, ' + test('Given nested parameterized groups with a registered path, ' 'when looking up the deeply nested route with parameter values, ' 'then it returns the correct value with all parameters extracted', () { final router = Router(); @@ -112,8 +110,7 @@ void main() { expectLookupResult(result, 'some-data', {#tenantId: 'abc', #userId: '123'}); }); - test( - 'Given a router with a group on a dynamic path "/:p", ' + test('Given a router with a group on a dynamic path "/:p", ' 'when attempting to add a groups with an incompatible path "*", ' 'then an ArgumentError is thrown', () { final router = Router(); @@ -121,8 +118,7 @@ void main() { expect(() => router.group('*'), throwsArgumentError); }); - test( - 'Given a router with a group on a path "a", ' + test('Given a router with a group on a path "a", ' 'when adding a group with a semantically equivalent path "///a//", ' 'then it succeeds', () { final router = Router(); @@ -130,8 +126,7 @@ void main() { expect(() => router.group('///a//'), returnsNormally); }); - test( - 'Given two group instances with the same path, ' + test('Given two group instances with the same path, ' 'when comparing them and accessing routes, ' 'then they are not equal but share the same underlying routes', () { final router = Router(); @@ -142,8 +137,7 @@ void main() { expectLookupResult(result, 1); }); - test( - 'Given a router with an empty group, ' + test('Given a router with an empty group, ' 'when looking up the group path directly, ' 'then no route is found', () { final router = Router()..group('a'); diff --git a/test/router/router_handler_test.dart b/test/router/router_handler_test.dart index bfb41ab3..7cdf3b4a 100644 --- a/test/router/router_handler_test.dart +++ b/test/router/router_handler_test.dart @@ -10,14 +10,15 @@ class _FakeRequest extends Fake implements Request { @override final Method method; - _FakeRequest(final String path, - {final String host = 'localhost', this.method = Method.get}) - : url = Uri.parse('http://$host$path'); + _FakeRequest( + final String path, { + final String host = 'localhost', + this.method = Method.get, + }) : url = Uri.parse('http://$host$path'); } void main() { - test( - 'Given a router with a GET route, ' + test('Given a router with a GET route, ' 'when calling router with matching GET request, ' 'then the handler is invoked and returns response', () async { final router = RelicRouter(); @@ -35,8 +36,7 @@ void main() { expect(result.response.statusCode, 200); }); - test( - 'Given a router with a parameterized route, ' + test('Given a router with a parameterized route, ' 'when calling router with matching request, ' 'then path parameters are accessible via context', () async { final router = RelicRouter(); @@ -56,8 +56,7 @@ void main() { expect(capturedAge, '30'); }); - test( - 'Given a router with a GET route, ' + test('Given a router with a GET route, ' 'when calling router with POST to same path, ' 'then returns 405 with Allow header', () async { final router = RelicRouter(); @@ -71,8 +70,7 @@ void main() { expect(result.response.headers.allow, contains(Method.get)); }); - test( - 'Given a router with multiple methods on same path, ' + test('Given a router with multiple methods on same path, ' 'when calling router with unregistered method, ' 'then returns 405 with all allowed methods in Allow header', () async { final router = RelicRouter(); @@ -85,11 +83,12 @@ void main() { expect(result.response.statusCode, 405); expect( - result.response.headers.allow, containsAll([Method.get, Method.post])); + result.response.headers.allow, + containsAll([Method.get, Method.post]), + ); }); - test( - 'Given a router with routes, ' + test('Given a router with routes, ' 'when calling router with unmatched path, ' 'then returns 404', () async { final router = RelicRouter(); @@ -102,8 +101,7 @@ void main() { expect(result.response.statusCode, 404); }); - test( - 'Given a router with fallback handler, ' + test('Given a router with fallback handler, ' 'when path misses in router, ' 'then fallback handler is called', () async { final router = RelicRouter(); @@ -125,8 +123,7 @@ void main() { expect(result.response.statusCode, 200); }); - test( - 'Given a router with tail segment route, ' + test('Given a router with tail segment route, ' 'when calling router with path matching tail, ' 'then remaining path is accessible via context', () async { final router = RelicRouter(); @@ -143,8 +140,7 @@ void main() { expect(capturedRemaining, '/css/main.css'); }); - test( - 'Given a router with nested routes, ' + test('Given a router with nested routes, ' 'when calling router with matching request, ' 'then matched path is accessible via context', () async { final router = RelicRouter(); @@ -161,8 +157,7 @@ void main() { expect(capturedMatched, '/api/v1/users'); }); - test( - 'Given a router with wildcard segment, ' + test('Given a router with wildcard segment, ' 'when calling router with matching path, ' 'then handler is invoked', () async { final router = RelicRouter(); @@ -179,8 +174,7 @@ void main() { expect(handlerCalled, isTrue); }); - test( - 'Given an empty router, ' + test('Given an empty router, ' 'when calling router with any request, ' 'then returns 404', () async { final router = RelicRouter(); @@ -192,8 +186,7 @@ void main() { expect(result.response.statusCode, 404); }); - test( - 'Given a router with use applied, ' + test('Given a router with use applied, ' 'when calling router with matching request, ' 'then middleware transformation is applied', () async { final router = RelicRouter(); @@ -216,8 +209,7 @@ void main() { expect(result.response.headers['X-Custom'], ['applied']); }); - test( - 'Given a router with fallback set, ' + test('Given a router with fallback set, ' 'when calling router with unmatched path, ' 'then fallback handler is called', () async { final router = RelicRouter(); @@ -226,8 +218,9 @@ void main() { var fallbackCalled = false; router.fallback = (final ctx) { fallbackCalled = true; - return ctx - .respond(Response.ok(body: Body.fromString('fallback response'))); + return ctx.respond( + Response.ok(body: Body.fromString('fallback response')), + ); }; final request = _FakeRequest('/nonexistent'); @@ -239,8 +232,7 @@ void main() { expect(await result.response.readAsString(), 'fallback response'); }); - test( - 'Given a router without fallback set, ' + test('Given a router without fallback set, ' 'when calling router with unmatched path, ' 'then returns 404', () async { final router = RelicRouter(); @@ -254,8 +246,7 @@ void main() { expect(result.response.statusCode, 404); }); - test( - 'Given a router with fallback set, ' + test('Given a router with fallback set, ' 'when calling router with method miss, ' 'then returns 405 (not fallback)', () async { final router = RelicRouter(); @@ -276,18 +267,19 @@ void main() { expect(result.response.headers.allow, contains(Method.get)); }); - test( - 'Given a router with fallback, ' + test('Given a router with fallback, ' 'when fallback is overwritten, ' 'then new fallback is used', () async { final router = RelicRouter(); router.get('/users', (final ctx) => ctx.respond(Response.ok())); - router.fallback = (final ctx) => - ctx.respond(Response.ok(body: Body.fromString('first fallback'))); + router.fallback = + (final ctx) => + ctx.respond(Response.ok(body: Body.fromString('first fallback'))); - router.fallback = (final ctx) => - ctx.respond(Response.ok(body: Body.fromString('second fallback'))); + router.fallback = + (final ctx) => + ctx.respond(Response.ok(body: Body.fromString('second fallback'))); final request = _FakeRequest('/nonexistent'); final ctx = request.toContext(Object()); @@ -296,15 +288,15 @@ void main() { expect(await result.response.readAsString(), 'second fallback'); }); - test( - 'Given a router with fallback set to null, ' + test('Given a router with fallback set to null, ' 'when calling router with unmatched path, ' 'then returns 404', () async { final router = RelicRouter(); router.get('/users', (final ctx) => ctx.respond(Response.ok())); - router.fallback = (final ctx) => - ctx.respond(Response.ok(body: Body.fromString('fallback'))); + router.fallback = + (final ctx) => + ctx.respond(Response.ok(body: Body.fromString('fallback'))); router.fallback = null; // Clear fallback final request = _FakeRequest('/nonexistent'); diff --git a/test/router/router_inject_test.dart b/test/router/router_inject_test.dart index 97f8cf48..923abfe2 100644 --- a/test/router/router_inject_test.dart +++ b/test/router/router_inject_test.dart @@ -6,15 +6,17 @@ import 'package:test/test.dart'; void main() { group('Router.inject with HandlerObject', () { - test( - 'Given a HandlerObject, ' + test('Given a HandlerObject, ' 'when injected into router, ' 'then it is registered with default injectIn behavior', () async { final router = Router(); router.inject(const _EchoHandlerObject()); - final request = Request(Method.post, Uri.parse('http://localhost/'), - body: Body.fromString('Hello from the other side')); + final request = Request( + Method.post, + Uri.parse('http://localhost/'), + body: Body.fromString('Hello from the other side'), + ); final ctx = request.toContext(Object()); final result = await router.asHandler(ctx) as ResponseContext; @@ -22,16 +24,17 @@ void main() { expect(await result.response.readAsString(), 'Hello from the other side'); }); - test( - 'Given a HandlerObject with custom injectIn, ' + test('Given a HandlerObject with custom injectIn, ' 'when injected into router, ' 'then custom path and method are used', () async { final router = Router(); router.inject(const _EchoHandlerObject(mountAt: '/custom/path')); final request = Request( - Method.post, Uri.parse('http://localhost/custom/path'), - body: Body.fromString('custom handler')); + Method.post, + Uri.parse('http://localhost/custom/path'), + body: Body.fromString('custom handler'), + ); final ctx = request.toContext(Object()); final result = await router.asHandler(ctx) as ResponseContext; @@ -42,37 +45,45 @@ void main() { group('Router.inject with MiddlewareObject', () { test( - 'Given a MiddlewareObject, ' - 'when injected into router, ' - 'then it is registered with default injectIn behavior at root path', - () async { - final router = Router(); - router.inject(_HeaderSetMiddlewareObject( - (final mh) => mh['X-Middleware'] = ['applied'])); - router.get('/test', (final ctx) => ctx.respond(Response.ok())); - - final request = Request(Method.get, Uri.parse('http://localhost/test')); - final ctx = request.toContext(Object()); - final result = await router.asHandler(ctx) as ResponseContext; - - expect(result.response.statusCode, 200); - expect(result.response.headers['X-Middleware'], ['applied']); - }); + 'Given a MiddlewareObject, ' + 'when injected into router, ' + 'then it is registered with default injectIn behavior at root path', + () async { + final router = Router(); + router.inject( + _HeaderSetMiddlewareObject( + (final mh) => mh['X-Middleware'] = ['applied'], + ), + ); + router.get('/test', (final ctx) => ctx.respond(Response.ok())); + + final request = Request(Method.get, Uri.parse('http://localhost/test')); + final ctx = request.toContext(Object()); + final result = await router.asHandler(ctx) as ResponseContext; + + expect(result.response.statusCode, 200); + expect(result.response.headers['X-Middleware'], ['applied']); + }, + ); - test( - 'Given a MiddlewareObject with custom injectIn, ' + test('Given a MiddlewareObject with custom injectIn, ' 'when injected into router, ' 'then custom path is used', () async { final router = Router(); - router.inject(_HeaderSetMiddlewareObject( + router.inject( + _HeaderSetMiddlewareObject( (final mh) => mh['X-Custom-Middleware'] = ['yes'], - mountAt: '/api')); + mountAt: '/api', + ), + ); router.get('/api/users', (final ctx) => ctx.respond(Response.ok())); router.get('/other', (final ctx) => ctx.respond(Response.ok())); // Should apply to /api/* paths - final apiRequest = - Request(Method.get, Uri.parse('http://localhost/api/users')); + final apiRequest = Request( + Method.get, + Uri.parse('http://localhost/api/users'), + ); final apiCtx = apiRequest.toContext(Object()); final apiResult = await router.asHandler(apiCtx) as ResponseContext; @@ -80,8 +91,10 @@ void main() { expect(apiResult.response.headers['X-Custom-Middleware'], ['yes']); // Should NOT apply to other paths - final otherRequest = - Request(Method.get, Uri.parse('http://localhost/other')); + final otherRequest = Request( + Method.get, + Uri.parse('http://localhost/other'), + ); final otherCtx = otherRequest.toContext(Object()); final otherResult = await router.asHandler(otherCtx) as ResponseContext; @@ -89,15 +102,20 @@ void main() { expect(otherResult.response.headers['X-Custom-Middleware'], isNull); }); - test( - 'Given multiple MiddlewareObjects, ' + test('Given multiple MiddlewareObjects, ' 'when injected into router, ' 'then they are all registered and compose correctly', () async { final router = Router(); - router.inject(_HeaderSetMiddlewareObject( - (final mh) => mh['X-Middleware'] = ['applied'])); - router.inject(_HeaderSetMiddlewareObject( - (final mh) => mh['X-Second'] = ['also-applied'])); + router.inject( + _HeaderSetMiddlewareObject( + (final mh) => mh['X-Middleware'] = ['applied'], + ), + ); + router.inject( + _HeaderSetMiddlewareObject( + (final mh) => mh['X-Second'] = ['also-applied'], + ), + ); router.get('/test', (final ctx) => ctx.respond(Response.ok())); final request = Request(Method.get, Uri.parse('http://localhost/test')); @@ -123,9 +141,7 @@ class _EchoHandlerObject extends HandlerObject { @override FutureOr call(final NewContext ctx) { final data = ctx.request.body.read(); - return ctx.respond( - Response.ok(body: Body.fromDataStream(data)), - ); + return ctx.respond(Response.ok(body: Body.fromDataStream(data))); } } @@ -145,8 +161,9 @@ class _HeaderSetMiddlewareObject extends MiddlewareObject { final result = await next(ctx); if (result is! ResponseContext) return result; return result.respond( - result.response - .copyWith(headers: result.response.headers.transform(update)), + result.response.copyWith( + headers: result.response.headers.transform(update), + ), ); }; } diff --git a/test/router/router_methods_test.dart b/test/router/router_methods_test.dart index ae660c39..4e5103ef 100644 --- a/test/router/router_methods_test.dart +++ b/test/router/router_methods_test.dart @@ -25,31 +25,29 @@ void main() { parameterizedGroup( variants: )>{ - Method.get: (final r) => r.get, - Method.head: (final r) => r.head, - Method.post: (final r) => r.post, - Method.put: (final r) => r.put, - Method.delete: (final r) => r.delete, - Method.patch: (final r) => r.patch, - Method.options: (final r) => r.options, - Method.trace: (final r) => r.trace, - Method.connect: (final r) => r.connect, - }.entries, + Method.get: (final r) => r.get, + Method.head: (final r) => r.head, + Method.post: (final r) => r.post, + Method.put: (final r) => r.put, + Method.delete: (final r) => r.delete, + Method.patch: (final r) => r.patch, + Method.options: (final r) => r.options, + Method.trace: (final r) => r.trace, + Method.connect: (final r) => r.connect, + }.entries, (final v) => 'Given router.${v.key.name} used to register a handler for a path,', (final v) { - test( - 'when looking up with ${v.key} for that path, ' - 'then returns the correct handler', - () { - v.value(router)('/path', 'handler'); // register handler - final result = router.lookup(v.key, '/path'); - expectLookupResult(result, 'handler'); - }, - ); + test('when looking up with ${v.key} for that path, ' + 'then returns the correct handler', () { + v.value(router)('/path', 'handler'); // register handler + final result = router.lookup(v.key, '/path'); + expectLookupResult(result, 'handler'); + }); parameterizedTest( - (final vv) => 'when looking up with $vv for that path, ' + (final vv) => + 'when looking up with $vv for that path, ' 'then returns PassMiss', (final vv) { final result = router.lookup(vv, '/path'); @@ -61,8 +59,7 @@ void main() { }, ); - test( - 'Given a GET handler registered for a path, ' + test('Given a GET handler registered for a path, ' 'when looking up with Method.post for the same path, ' 'then returns MethodMiss', () { router.get('/specific-method-mismatch', 'get_handler'); @@ -71,8 +68,7 @@ void main() { }); group('ANY Method Registration (router.any)', () { - test( - 'Given router.any() used to register a handler, ' + test('Given router.any() used to register a handler, ' 'when looking up with Method.get for that path, ' 'then returns the handler registered by any()', () { router.any('/any-path', 'any_handler'); @@ -80,8 +76,7 @@ void main() { expectLookupResult(result, 'any_handler'); }); - test( - 'Given router.any() used to register a handler, ' + test('Given router.any() used to register a handler, ' 'when looking up with Method.post for that path, ' 'then returns the handler registered by any()', () { router.any('/any-path', 'any_handler'); @@ -89,8 +84,7 @@ void main() { expectLookupResult(result, 'any_handler'); }); - test( - 'Given router.any() used to register a handler, ' + test('Given router.any() used to register a handler, ' 'when looking up with all defined methods for that path, ' 'then returns the handler registered by any() for each method', () { router.any('/any-path-all-methods', 'any_handler_all'); @@ -102,8 +96,7 @@ void main() { }); group('Method Registration Conflicts', () { - test( - 'Given a GET handler registered via router.get(), ' + test('Given a GET handler registered via router.get(), ' 'when attempting to register another GET handler for the same path via router.get(), ' 'then throws ArgumentError', () { router.get('/conflict-path', 'get_handler_1'); @@ -113,8 +106,7 @@ void main() { ); }); - test( - 'Given a GET handler registered via router.add(), ' + test('Given a GET handler registered via router.add(), ' 'when attempting to register another GET handler for the same path via router.add(), ' 'then throws ArgumentError', () { router.add(Method.get, '/conflict-add-path', 'get_handler_add_1'); @@ -125,8 +117,7 @@ void main() { ); }); - test( - 'Given router.any() used for a path, ' + test('Given router.any() used for a path, ' 'when attempting to register a specific GET handler for the same path, ' 'then throws ArgumentError', () { router.any('/any-conflict-path', 'any_handler_conflict'); @@ -137,39 +128,45 @@ void main() { }); test( - 'Given a specific GET handler registered for a path, ' - 'when attempting to use router.any() for the same path, ' - 'then throws ArgumentError (on the first conflicting method, which is GET)', - () { - router.get('/specific-conflict-path', 'get_handler_specific_conflict'); - expect( - () => router.any( - '/specific-conflict-path', 'any_handler_specific_conflict'), - throwsArgumentError, - ); - }); - - test( - 'Given GET and POST handlers registered for different paths, ' - 'when looking them up, ' - 'then each returns their correct handler (no cross-path interference)', - () { - router.get('/path-alpha', 'get_alpha'); - router.post('/path-beta', 'post_beta'); - - final resultAlpha = router.lookup(Method.get, '/path-alpha'); - expectLookupResult(resultAlpha, 'get_alpha'); - expect(router.lookup(Method.post, '/path-alpha'), isA()); - - final resultBeta = router.lookup(Method.post, '/path-beta'); - expectLookupResult(resultBeta, 'post_beta'); - expect(router.lookup(Method.get, '/path-beta'), isA()); - }); + 'Given a specific GET handler registered for a path, ' + 'when attempting to use router.any() for the same path, ' + 'then throws ArgumentError (on the first conflicting method, which is GET)', + () { + router.get( + '/specific-conflict-path', + 'get_handler_specific_conflict', + ); + expect( + () => router.any( + '/specific-conflict-path', + 'any_handler_specific_conflict', + ), + throwsArgumentError, + ); + }, + ); + + test( + 'Given GET and POST handlers registered for different paths, ' + 'when looking them up, ' + 'then each returns their correct handler (no cross-path interference)', + () { + router.get('/path-alpha', 'get_alpha'); + router.post('/path-beta', 'post_beta'); + + final resultAlpha = router.lookup(Method.get, '/path-alpha'); + expectLookupResult(resultAlpha, 'get_alpha'); + expect(router.lookup(Method.post, '/path-alpha'), isA()); + + final resultBeta = router.lookup(Method.post, '/path-beta'); + expectLookupResult(resultBeta, 'post_beta'); + expect(router.lookup(Method.get, '/path-beta'), isA()); + }, + ); }); group('Method Handling with Parameters', () { - test( - 'Given a GET handler for a parameterized path /users/:id, ' + test('Given a GET handler for a parameterized path /users/:id, ' 'when looking up with Method.get and a matching path, ' 'then returns the handler and correct parameters', () { router.get('/users/:id', 'get_user_handler'); @@ -178,30 +175,33 @@ void main() { }); test( - 'Given a POST handler for a parameterized path /posts/:postId/comments, ' - 'when looking up with Method.post and a matching path, ' - 'then returns the handler and correct parameters', () { - router.post('/posts/:postId/comments', 'post_comment_handler'); - final result = router.lookup(Method.post, '/posts/abc/comments'); - expectLookupResult(result, 'post_comment_handler', {#postId: 'abc'}); - }); + 'Given a POST handler for a parameterized path /posts/:postId/comments, ' + 'when looking up with Method.post and a matching path, ' + 'then returns the handler and correct parameters', + () { + router.post('/posts/:postId/comments', 'post_comment_handler'); + final result = router.lookup(Method.post, '/posts/abc/comments'); + expectLookupResult(result, 'post_comment_handler', {#postId: 'abc'}); + }, + ); test( - 'Given GET and POST handlers for the same parameterized path /data/:key, ' - 'when looking up with Method.get, then returns GET handler, ' - 'when looking up with Method.post, then returns POST handler', () { - router.get('/data/:key', 'get_data_handler'); - router.post('/data/:key', 'post_data_handler'); + 'Given GET and POST handlers for the same parameterized path /data/:key, ' + 'when looking up with Method.get, then returns GET handler, ' + 'when looking up with Method.post, then returns POST handler', + () { + router.get('/data/:key', 'get_data_handler'); + router.post('/data/:key', 'post_data_handler'); - final getResult = router.lookup(Method.get, '/data/xyz'); - expectLookupResult(getResult, 'get_data_handler', {#key: 'xyz'}); + final getResult = router.lookup(Method.get, '/data/xyz'); + expectLookupResult(getResult, 'get_data_handler', {#key: 'xyz'}); - final postResult = router.lookup(Method.post, '/data/xyz'); - expectLookupResult(postResult, 'post_data_handler', {#key: 'xyz'}); - }); + final postResult = router.lookup(Method.post, '/data/xyz'); + expectLookupResult(postResult, 'post_data_handler', {#key: 'xyz'}); + }, + ); - test( - 'Given a GET handler for parameterized path /files/:name, ' + test('Given a GET handler for parameterized path /files/:name, ' 'when looking up with Method.put for the same path pattern, ' 'then returns MethodMiss', () { router.get('/files/:name', 'get_file_handler'); @@ -209,8 +209,7 @@ void main() { expect(result, isA()); }); - test( - 'Given router.any() for a parameterized path /items/:itemId, ' + test('Given router.any() for a parameterized path /items/:itemId, ' 'when looking up with Method.delete and matching path, ' 'then returns the any_handler and correct parameters', () { router.any('/items/:itemId', 'any_item_handler'); @@ -220,16 +219,14 @@ void main() { }); group('Lookup for Unregistered Paths/Methods', () { - test( - 'Given an empty router, ' + test('Given an empty router, ' 'when looking up any path with any method, ' 'then returns PathMiss', () { final result = router.lookup(Method.get, '/nonexistent'); expect(result, isA()); }); - test( - 'Given a router with some registrations, ' + test('Given a router with some registrations, ' 'when looking up a completely different path, ' 'then returns PathMiss', () { router.get('/exists', 'handler_exists'); @@ -237,8 +234,7 @@ void main() { expect(result, isA()); }); - test( - 'Given a GET handler for /path, ' + test('Given a GET handler for /path, ' 'when looking up /path with Method.post, ' 'then returns MethodMiss', () { router.get('/another-path', 'get_another'); diff --git a/test/router/router_test.dart b/test/router/router_test.dart index da4d0e16..b59e0acf 100644 --- a/test/router/router_test.dart +++ b/test/router/router_test.dart @@ -26,52 +26,45 @@ void main() { expect(router.isEmpty, isTrue); }); - test( - 'when looking up an empty string, ' + test('when looking up an empty string, ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, ''); expect(result, isA()); }); - test( - 'when looking up a non-empty string, ' + test('when looking up a non-empty string, ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/hello'); expect(result, isA()); }); - test( - 'when looking up the root path "/", ' + test('when looking up the root path "/", ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/'); expect(result, isA()); }); - test( - 'when adding the root path "/", ' + test('when adding the root path "/", ' 'then lookup for "/" should return the correct value', () { router.get('/', 'root_handler'); final result = router.lookup(Method.get, '/'); expectLookupResult(result, 'root_handler'); }); - test( - 'when adding a route, ' + test('when adding a route, ' 'then isEmpty should be false', () { router.get('/test', 'test_handler'); expect(router.isEmpty, isFalse); }); - test( - 'when adding an empty string path, ' + test('when adding an empty string path, ' 'then lookup for "/" should return the correct value', () { router.get('', 'empty_string_handler'); final result = router.lookup(Method.get, '/'); expectLookupResult(result, 'empty_string_handler'); }); - test( - 'when adding an empty string path, ' + test('when adding an empty string path, ' 'then lookup for "" should return the correct value', () { router.get('', 'empty_string_handler'); final result = router.lookup(Method.get, ''); @@ -89,22 +82,19 @@ void main() { router.get(path, value); }); - test( - 'when looking up the exact path, ' + test('when looking up the exact path, ' 'then it should return the correct value', () { final result = router.lookup(Method.get, path); expectLookupResult(result, value); }); - test( - 'when looking up the path with a trailing slash, ' + test('when looking up the path with a trailing slash, ' 'then it should return the correct value', () { final result = router.lookup(Method.get, '$path/'); expectLookupResult(result, value); }); - test( - 'when looking up a different path, ' + test('when looking up a different path, ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/world'); expect(result, isA()); @@ -121,15 +111,13 @@ void main() { router.get(path, value); }); - test( - 'when looking up the exact path, ' + test('when looking up the exact path, ' 'then it should return the correct value', () { final result = router.lookup(Method.get, path); expectLookupResult(result, value); }); - test( - 'when looking up a prefix of the path, ' + test('when looking up a prefix of the path, ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/admin/users'); expect(result, isA()); @@ -147,15 +135,13 @@ void main() { router.get(pathWithSlash, value); }); - test( - 'when looking up the path without the slash, ' + test('when looking up the path without the slash, ' 'then it should return the correct value', () { final result = router.lookup(Method.get, pathWithoutSlash); expectLookupResult(result, value); }); - test( - 'when looking up the path with the slash, ' + test('when looking up the path with the slash, ' 'then it should return the correct value', () { final result = router.lookup(Method.get, pathWithSlash); expectLookupResult(result, value); @@ -175,15 +161,13 @@ void main() { router.get(pathWithoutSlash, value); }); - test( - 'when looking up the path without the slash, ' + test('when looking up the path without the slash, ' 'then it should return the correct value', () { final result = router.lookup(Method.get, pathWithoutSlash); expectLookupResult(result, value); }); - test( - 'when looking up the path with the slash ' + test('when looking up the path with the slash ' 'then it should return the correct value', () { final result = router.lookup(Method.get, pathWithSlash); expectLookupResult(result, value); @@ -202,8 +186,7 @@ void main() { router.get(path, firstValue); }); - test( - 'when adding the same path again, ' + test('when adding the same path again, ' 'then it should throw ArgumentError and retain the original value', () { // Check that the first value is present var result = router.lookup(Method.get, path); @@ -217,8 +200,7 @@ void main() { expectLookupResult(result, firstValue); }); - test( - 'when adding a path that normalizes to the same path ' + test('when adding a path that normalizes to the same path ' 'then it should throw ArgumentError', () { const equivalentPath = '$path/'; // Normalizes to the same as path expect( @@ -237,22 +219,19 @@ void main() { router.get(pattern, value); }); - test( - 'when looking up a matching path, ' + test('when looking up a matching path, ' 'then it should return the value and parameter', () { final result = router.lookup(Method.get, '/users/123'); expectLookupResult(result, value, {#id: '123'}); }); - test( - 'when looking up a non-matching path structure, ' + test('when looking up a non-matching path structure, ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/posts/123'); expect(result, isA()); }); - test( - 'when looking up a path matching only the prefix, ' + test('when looking up a path matching only the prefix, ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/users'); expect(result, isA()); @@ -268,15 +247,13 @@ void main() { router.get(pattern, value); }); - test( - 'when looking up a matching path, ' + test('when looking up a matching path, ' 'then it should return the value and parameter', () { final result = router.lookup(Method.get, '/report.pdf'); expectLookupResult(result, value, {#filename: 'report.pdf'}); }); - test( - 'when looking up the root path "/", ' + test('when looking up the root path "/", ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/'); expect(result, isA()); @@ -292,15 +269,13 @@ void main() { router.get(pattern, value); }); - test( - 'when looking up a matching path, ' + test('when looking up a matching path, ' 'then it should return the value and parameters', () { final result = router.lookup(Method.get, '/users/abc/items/xyz'); expectLookupResult(result, value, {#userId: 'abc', #itemId: 'xyz'}); }); - test( - 'when looking up a path matching only the first parameter section, ' + test('when looking up a path matching only the first parameter section, ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/users/abc'); expect(result, isA()); @@ -326,15 +301,13 @@ void main() { router.get('/products/:productId/reviews/:reviewId', 'product_review'); }); - test( - 'when looking up the details route, ' + test('when looking up the details route, ' 'then it should match correctly', () { final result = router.lookup(Method.get, '/products/p100/details'); expectLookupResult(result, 'product_details', {#productId: 'p100'}); }); - test( - 'when looking up the reviews route, ' + test('when looking up the reviews route, ' 'then it should match correctly', () { final result = router.lookup(Method.get, '/products/p200/reviews/r50'); expectLookupResult(result, 'product_review', { @@ -361,22 +334,19 @@ void main() { router.get(postPattern, postValue); }); - test( - 'when looking up the first pattern, ' + test('when looking up the first pattern, ' 'then it should match correctly', () { final result = router.lookup(Method.get, '/users/1'); expectLookupResult(result, userValue, {#id: '1'}); }); - test( - 'when looking up the second pattern ' + test('when looking up the second pattern ' 'then it should match correctly', () { final result = router.lookup(Method.get, '/posts/abc'); expectLookupResult(result, postValue, {#postId: 'abc'}); }); - test( - 'when looking up a different initial segment ' + test('when looking up a different initial segment ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/articles/xyz'); expect(result, isA()); @@ -396,15 +366,13 @@ void main() { router.get(patternWithSlash, value); }); - test( - 'when looking up without trailing slash ' + test('when looking up without trailing slash ' 'then it should match', () { final result = router.lookup(Method.get, pathWithoutSlash); expectLookupResult(result, value, {#param: 'value1'}); }); - test( - 'when looking up with trailing slash ' + test('when looking up with trailing slash ' 'then it should match', () { final result = router.lookup(Method.get, pathWithSlash); expectLookupResult(result, value, {#param: 'value1'}); @@ -425,15 +393,13 @@ void main() { router.get(patternWithoutSlash, value); }); - test( - 'when looking up without trailing slash ' + test('when looking up without trailing slash ' 'then it should match', () { final result = router.lookup(Method.get, pathWithoutSlash); expectLookupResult(result, value, {#param: 'value2'}); }); - test( - 'when looking up with trailing slash ' + test('when looking up with trailing slash ' 'then it should match', () { final result = router.lookup(Method.get, pathWithSlash); expectLookupResult(result, value, {#param: 'value2'}); @@ -452,8 +418,7 @@ void main() { router.get(path, value1); }); - test( - 'when adding the same dynamic path again ' + test('when adding the same dynamic path again ' 'then it should throw ArgumentError and retain original', () { // Check original is present var result = router.lookup(Method.get, '/items/111'); @@ -482,8 +447,7 @@ void main() { router.get(pathId, valueId); }); - test( - 'when adding a conflicting dynamic route ' + test('when adding a conflicting dynamic route ' 'then it should throw ArgumentError and retain original', () { // Check original is present var result = router.lookup(Method.get, '/content/123'); @@ -507,15 +471,13 @@ void main() { router.get('/users/:id', 'dynamic_user'); // Dynamic }); - test( - 'when looking up the exact static path ' + test('when looking up the exact static path ' 'then the static route should be prioritized', () { final result = router.lookup(Method.get, '/users/profile'); expectLookupResult(result, 'static_profile'); }); - test( - 'when looking up a path matching the dynamic pattern ' + test('when looking up a path matching the dynamic pattern ' 'then the dynamic route should be used', () { final result = router.lookup(Method.get, '/users/other'); expectLookupResult(result, 'dynamic_user', {#id: 'other'}); @@ -530,15 +492,13 @@ void main() { router.get('/data/:version', 'dynamic_version'); // Dynamic }); - test( - 'when looking up the static path ' + test('when looking up the static path ' 'then it should match the static route', () { final result = router.lookup(Method.get, '/data/latest'); expectLookupResult(result, 'static_latest'); }); - test( - 'when looking up a path matching the dynamic pattern ' + test('when looking up a path matching the dynamic pattern ' 'then it should match the dynamic route', () { final result = router.lookup(Method.get, '/data/v1.2'); expectLookupResult(result, 'dynamic_version', {#version: 'v1.2'}); @@ -553,8 +513,7 @@ void main() { router.get('/static2/path', 's2'); }); - test( - 'when looking up a path that looks dynamic ' + test('when looking up a path that looks dynamic ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/static1/123'); expect(result, isA()); @@ -569,8 +528,7 @@ void main() { router.get('/another/:key/value', 'd2'); }); - test( - 'when looking up a static path, ' + test('when looking up a static path, ' 'then it should return PathMiss', () { final result = router.lookup(Method.get, '/static/path'); expect(result, isA()); @@ -590,17 +548,17 @@ void main() { router.get('/posts/:postId/', 'handler6'); // Dynamic with trailing }); - test( - 'when looking up paths with various slash combinations ' + test('when looking up paths with various slash combinations ' 'then normalization should ensure correct matches', () { expectLookupResult(router.lookup(Method.get, '/path1'), 'handler1'); expectLookupResult(router.lookup(Method.get, '/path2'), 'handler2'); expectLookupResult(router.lookup(Method.get, '/path3'), 'handler3'); expectLookupResult(router.lookup(Method.get, '/path4'), 'handler4'); expectLookupResult( - router.lookup(Method.get, '/users/abc/items'), 'handler5', { - #id: 'abc', - }); + router.lookup(Method.get, '/users/abc/items'), + 'handler5', + {#id: 'abc'}, + ); expectLookupResult(router.lookup(Method.get, '/posts/xyz'), 'handler6', { #postId: 'xyz', }); @@ -610,9 +568,10 @@ void main() { expectLookupResult(router.lookup(Method.get, '/path3/'), 'handler3'); expectLookupResult(router.lookup(Method.get, 'path4'), 'handler4'); expectLookupResult( - router.lookup(Method.get, '/users/abc/items/'), 'handler5', { - #id: 'abc', - }); + router.lookup(Method.get, '/users/abc/items/'), + 'handler5', + {#id: 'abc'}, + ); expectLookupResult(router.lookup(Method.get, '/posts/xyz/'), 'handler6', { #postId: 'xyz', }); @@ -632,45 +591,58 @@ void main() { setUp(() { subRouter.get('/child', 'child_handler'); subRouter.get( - '', 'sub_root_handler'); // Handler at the root of subRouter + '', + 'sub_root_handler', + ); // Handler at the root of subRouter mainRouter.attach('/parent', subRouter); }); test('then its /child route is accessible via /parent/child', () { expectLookupResult( - mainRouter.lookup(Method.get, '/parent/child'), 'child_handler'); + mainRouter.lookup(Method.get, '/parent/child'), + 'child_handler', + ); }); test( - 'then its /child route (trailing slash) is accessible via /parent/child/', - () { - expectLookupResult( - mainRouter.lookup(Method.get, '/parent/child/'), 'child_handler'); - }); + 'then its /child route (trailing slash) is accessible via /parent/child/', + () { + expectLookupResult( + mainRouter.lookup(Method.get, '/parent/child/'), + 'child_handler', + ); + }, + ); test('then its root route is accessible via /parent', () { expectLookupResult( - mainRouter.lookup(Method.get, '/parent'), 'sub_root_handler'); + mainRouter.lookup(Method.get, '/parent'), + 'sub_root_handler', + ); }); - test('then its root route (trailing slash) is accessible via /parent/', - () { - expectLookupResult( - mainRouter.lookup(Method.get, '/parent/'), 'sub_root_handler'); - }); + test( + 'then its root route (trailing slash) is accessible via /parent/', + () { + expectLookupResult( + mainRouter.lookup(Method.get, '/parent/'), + 'sub_root_handler', + ); + }, + ); test( - 'then a non-existent sub-route (/parent/nonexistent) returns PathMiss', - () { - expect( - mainRouter.lookup(Method.get, '/parent/nonexistent'), - isA(), - ); - }); + 'then a non-existent sub-route (/parent/nonexistent) returns PathMiss', + () { + expect( + mainRouter.lookup(Method.get, '/parent/nonexistent'), + isA(), + ); + }, + ); }); - test( - 'when a sub-route conflicts with an existing direct route, ' + test('when a sub-route conflicts with an existing direct route, ' 'then it throws and original route remains', () { mainRouter.get('/parent/existing_child', 'direct_child_handler_on_main'); subRouter.get('/existing_child', 'sub_router_child_handler'); @@ -683,8 +655,9 @@ void main() { // Ensure the original direct route on mainRouter is preserved expectLookupResult( - mainRouter.lookup(Method.get, '/parent/existing_child'), - 'direct_child_handler_on_main'); + mainRouter.lookup(Method.get, '/parent/existing_child'), + 'direct_child_handler_on_main', + ); }); }); } diff --git a/test/router/router_use_test.dart b/test/router/router_use_test.dart index 53731c2e..2b77f97c 100644 --- a/test/router/router_use_test.dart +++ b/test/router/router_use_test.dart @@ -2,8 +2,7 @@ import 'package:relic/relic.dart'; import 'package:test/test.dart'; void main() { - test( - 'Given a route at root, ' + test('Given a route at root, ' 'when use is applied to root, ' 'then the value is transformed', () { final router = Router(); @@ -13,8 +12,7 @@ void main() { expect(result.value, 2, reason: 'Should double'); }); - test( - 'Given a route at path, ' + test('Given a route at path, ' 'when use is applied twice to path, ' 'then the mappings compose', () { final router = Router(); @@ -25,8 +23,7 @@ void main() { expect(result.value, 8, reason: 'Should add 3 and then double'); }); - test( - 'Given an empty router, ' + test('Given an empty router, ' 'when use is applied before add, ' 'then the value is transformed', () { final router = Router(); @@ -36,8 +33,7 @@ void main() { expect(result.value, 2, reason: 'Should double'); }); - test( - 'Given routes at multiple paths, ' + test('Given routes at multiple paths, ' 'when use is applied to root, ' 'then all descendant routes are transformed', () { final router = Router(); @@ -45,16 +41,24 @@ void main() { router.get('/a', 10); router.get('/b', 100); router.use('/', (final i) => i * 2); - expect((router.lookup(Method.get, '/') as RouterMatch).value, 2, - reason: 'Should double'); - expect((router.lookup(Method.get, '/a') as RouterMatch).value, 20, - reason: 'Should double'); - expect((router.lookup(Method.get, '/b') as RouterMatch).value, 200, - reason: 'Should double'); + expect( + (router.lookup(Method.get, '/') as RouterMatch).value, + 2, + reason: 'Should double', + ); + expect( + (router.lookup(Method.get, '/a') as RouterMatch).value, + 20, + reason: 'Should double', + ); + expect( + (router.lookup(Method.get, '/b') as RouterMatch).value, + 200, + reason: 'Should double', + ); }); - test( - 'Given routes at multiple paths, ' + test('Given routes at multiple paths, ' 'when use is applied to a specific path, ' 'then only descendants of that path are transformed', () { final router = Router(); @@ -62,16 +66,24 @@ void main() { router.get('/a', 10); router.get('/b', 100); router.use('/a', (final i) => i * 2); - expect((router.lookup(Method.get, '/') as RouterMatch).value, 1, - reason: 'Should not change'); - expect((router.lookup(Method.get, '/a') as RouterMatch).value, 20, - reason: 'Should double'); - expect((router.lookup(Method.get, '/b') as RouterMatch).value, 100, - reason: 'Should not change'); + expect( + (router.lookup(Method.get, '/') as RouterMatch).value, + 1, + reason: 'Should not change', + ); + expect( + (router.lookup(Method.get, '/a') as RouterMatch).value, + 20, + reason: 'Should double', + ); + expect( + (router.lookup(Method.get, '/b') as RouterMatch).value, + 100, + reason: 'Should not change', + ); }); - test( - 'Given routes with different HTTP methods, ' + test('Given routes with different HTTP methods, ' 'when use is applied, ' 'then all methods at that path are transformed', () { final router = Router(); @@ -84,8 +96,7 @@ void main() { expect((router.lookup(Method.put, '/api') as RouterMatch).value, 30); }); - test( - 'Given two routers where one is attached to the other, ' + test('Given two routers where one is attached to the other, ' 'when use is applied to the prefix on the parent, ' 'then only attached router routes are transformed', () { final routerA = Router(); @@ -94,8 +105,11 @@ void main() { routerB.get('/b', 100); routerA.attach('/prefix', routerB); routerA.use('/prefix', (final i) => i * 2); - expect((routerA.lookup(Method.get, '/a') as RouterMatch).value, 10, - reason: 'Should not change'); + expect( + (routerA.lookup(Method.get, '/a') as RouterMatch).value, + 10, + reason: 'Should not change', + ); expect( (routerA.lookup(Method.get, '/prefix/b') as RouterMatch).value, 200, @@ -103,8 +117,7 @@ void main() { ); }); - test( - 'Given two routers with use applied, ' + test('Given two routers with use applied, ' 'when attaching one to the other such that use collide, ' 'then map functions are composed', () { final routerA = Router(); @@ -120,8 +133,7 @@ void main() { ); }); - test( - 'Given hierarchical use mappings, ' + test('Given hierarchical use mappings, ' 'when looking up a route, ' 'then transformations are applied from leaf to root', () { final router = Router(); @@ -135,8 +147,7 @@ void main() { ); }); - test( - 'Given a router of middleware functions with hierarchical use, ' + test('Given a router of middleware functions with hierarchical use, ' 'when looking up and applying the route function, ' 'then the call order is root to leaf', () { final router = Router(); @@ -144,11 +155,9 @@ void main() { router.use('/a/b', (final next) => (final s) => '${next(s)}'); router.use('/a/b/c', (final next) => (final s) => '${next(s)}'); router.get('/a/b/c/d', (final s) => s); - final result = router.lookup(Method.get, '/a/b/c/d') - as RouterMatch; - expect( - result.value('request'), - 'request', - ); + final result = + router.lookup(Method.get, '/a/b/c/d') + as RouterMatch; + expect(result.value('request'), 'request'); }); } diff --git a/test/src/adapter/context_test.dart b/test/src/adapter/context_test.dart index d15c7dee..8ea29065 100644 --- a/test/src/adapter/context_test.dart +++ b/test/src/adapter/context_test.dart @@ -3,110 +3,127 @@ import 'package:relic/src/adapter/context.dart'; import 'package:test/test.dart'; void main() { - group('Given a NewContext, when withRequest is called with a new Request,', - () { - late NewContext context; - late Request originalRequest; - late Request newRequest; - late NewContext newContext; - late Object token; - - setUp(() { - originalRequest = Request(Method.get, Uri.parse('http://test.com/path')); - token = Object(); - context = originalRequest.toContext(token); - newRequest = Request(Method.post, Uri.parse('http://test.com/newpath')); - newContext = context.withRequest(newRequest); - }); - - test('then it returns a NewContext instance', () { - expect(newContext, isA()); - }); - - test('then the new context contains the new request', () { - expect(newContext.request, same(newRequest)); - }); - - test('then the new context preserves the same token', () { - expect(newContext.token, same(token)); - }); - - test('then the new context is not the same instance as the original', () { - expect(newContext, isNot(same(context))); - }); - - test('then the original context remains unchanged', () { - expect(context.request, same(originalRequest)); - expect(context.token, same(token)); - }); - - test('then the new context can transition to ResponseContext', () { - final responseContext = - newContext.respond(Response.ok(body: Body.fromString('test'))); - - expect(responseContext, isA()); - expect(responseContext.request, same(newRequest)); - expect(responseContext.token, same(token)); - }); - - test('then the new context can transition to HijackContext', () { - final hijackContext = newContext.hijack((final channel) {}); - - expect(hijackContext, isA()); - expect(hijackContext.request, same(newRequest)); - expect(hijackContext.token, same(token)); - }); - - test('then the new context can transition to ConnectContext', () { - final connectContext = newContext.connect((final webSocket) {}); - - expect(connectContext, isA()); - expect(connectContext.request, same(newRequest)); - expect(connectContext.token, same(token)); - }); - }); - group( - 'Given a NewContext, when withRequest is called with a request created using copyWith,', - () { - late NewContext context; - late Request originalRequest; - late Object token; - - setUp(() { - originalRequest = Request(Method.get, Uri.parse('http://test.com/path')); - token = Object(); - context = originalRequest.toContext(token); - }); - - test('then it simplifies middleware request rewriting pattern', () { - final rewrittenRequest = originalRequest.copyWith( - requestedUri: Uri.parse('http://test.com/rewritten'), - ); - final newContext = context.withRequest(rewrittenRequest); - - expect(newContext, isA()); - expect(newContext.request.requestedUri, - Uri.parse('http://test.com/rewritten')); - expect(newContext.token, same(token)); - }); + 'Given a NewContext, when withRequest is called with a new Request,', + () { + late NewContext context; + late Request originalRequest; + late Request newRequest; + late NewContext newContext; + late Object token; + + setUp(() { + originalRequest = Request( + Method.get, + Uri.parse('http://test.com/path'), + ); + token = Object(); + context = originalRequest.toContext(token); + newRequest = Request(Method.post, Uri.parse('http://test.com/newpath')); + newContext = context.withRequest(newRequest); + }); + + test('then it returns a NewContext instance', () { + expect(newContext, isA()); + }); + + test('then the new context contains the new request', () { + expect(newContext.request, same(newRequest)); + }); + + test('then the new context preserves the same token', () { + expect(newContext.token, same(token)); + }); + + test('then the new context is not the same instance as the original', () { + expect(newContext, isNot(same(context))); + }); + + test('then the original context remains unchanged', () { + expect(context.request, same(originalRequest)); + expect(context.token, same(token)); + }); + + test('then the new context can transition to ResponseContext', () { + final responseContext = newContext.respond( + Response.ok(body: Body.fromString('test')), + ); + + expect(responseContext, isA()); + expect(responseContext.request, same(newRequest)); + expect(responseContext.token, same(token)); + }); + + test('then the new context can transition to HijackContext', () { + final hijackContext = newContext.hijack((final channel) {}); + + expect(hijackContext, isA()); + expect(hijackContext.request, same(newRequest)); + expect(hijackContext.token, same(token)); + }); + + test('then the new context can transition to ConnectContext', () { + final connectContext = newContext.connect((final webSocket) {}); + + expect(connectContext, isA()); + expect(connectContext.request, same(newRequest)); + expect(connectContext.token, same(token)); + }); + }, + ); - test('then it maintains the same token across multiple transformations', + group( + 'Given a NewContext, when withRequest is called with a request created using copyWith,', + () { + late NewContext context; + late Request originalRequest; + late Object token; + + setUp(() { + originalRequest = Request( + Method.get, + Uri.parse('http://test.com/path'), + ); + token = Object(); + context = originalRequest.toContext(token); + }); + + test('then it simplifies middleware request rewriting pattern', () { + final rewrittenRequest = originalRequest.copyWith( + requestedUri: Uri.parse('http://test.com/rewritten'), + ); + final newContext = context.withRequest(rewrittenRequest); + + expect(newContext, isA()); + expect( + newContext.request.requestedUri, + Uri.parse('http://test.com/rewritten'), + ); + expect(newContext.token, same(token)); + }); + + test( + 'then it maintains the same token across multiple transformations', () { - final request1 = originalRequest.copyWith( - requestedUri: Uri.parse('http://test.com/step1'), - ); - final context1 = context.withRequest(request1); - - final request2 = request1.copyWith( - requestedUri: Uri.parse('http://test.com/step2'), + final request1 = originalRequest.copyWith( + requestedUri: Uri.parse('http://test.com/step1'), + ); + final context1 = context.withRequest(request1); + + final request2 = request1.copyWith( + requestedUri: Uri.parse('http://test.com/step2'), + ); + final context2 = context1.withRequest(request2); + + expect(context.token, same(token)); + expect(context1.token, same(token)); + expect(context2.token, same(token)); + expect( + context2.request.requestedUri, + Uri.parse('http://test.com/step2'), + ); + }, ); - final context2 = context1.withRequest(request2); - - expect(context.token, same(token)); - expect(context1.token, same(token)); - expect(context2.token, same(token)); - expect(context2.request.requestedUri, Uri.parse('http://test.com/step2')); - }); - }); + }, + ); } diff --git a/test/src/middleware/context_property_test.dart b/test/src/middleware/context_property_test.dart index b84e48c5..2e5326b4 100644 --- a/test/src/middleware/context_property_test.dart +++ b/test/src/middleware/context_property_test.dart @@ -17,8 +17,7 @@ void main() { context = _createContextInstance('singleCtx'); }); - test( - 'when a value is set for the context using the property, ' + test('when a value is set for the context using the property, ' 'then the same value can be retrieved using [].', () { const value = 'hello world'; stringProperty[context] = value; @@ -26,31 +25,30 @@ void main() { expect(retrievedValue, value); }); - test( - 'when no value is set for the context, ' + test('when no value is set for the context, ' 'then getOrNull returns null.', () { final result = stringProperty.getOrNull(context); expect(result, isNull); }); test( - 'when a value is set for the context and then retrieved using getOrNull, ' - 'then the originally set value is returned.', () { - const value = 'test value'; - stringProperty[context] = value; - final retrievedValue = stringProperty.getOrNull(context); - expect(retrievedValue, value); - }); - - test( - 'when no value is set for the context, ' + 'when a value is set for the context and then retrieved using getOrNull, ' + 'then the originally set value is returned.', + () { + const value = 'test value'; + stringProperty[context] = value; + final retrievedValue = stringProperty.getOrNull(context); + expect(retrievedValue, value); + }, + ); + + test('when no value is set for the context, ' 'then exists returns false.', () { final result = stringProperty.exists(context); expect(result, isFalse); }); - test( - 'when a value is set for the context and exists is called, ' + test('when a value is set for the context and exists is called, ' 'then true is returned.', () { const value = 'exists'; stringProperty[context] = value; @@ -58,75 +56,87 @@ void main() { expect(result, isTrue); }); - test( - 'when a value is set and then clear is called for the context, ' + test('when a value is set and then clear is called for the context, ' 'then the value is removed and subsequent accesses reflect this.', () { stringProperty[context] = 'to be cleared'; - expect(stringProperty.exists(context), isTrue, - reason: 'Pre-condition: value should exist'); + expect( + stringProperty.exists(context), + isTrue, + reason: 'Pre-condition: value should exist', + ); stringProperty.clear(context); expect(stringProperty.exists(context), isFalse); expect(stringProperty.getOrNull(context), isNull); - expect( - () => stringProperty[context], - throwsStateError, - ); + expect(() => stringProperty[context], throwsStateError); }); - test( - 'when a value is set and then clear is called, ' + test('when a value is set and then clear is called, ' 'then exists returns true after setting and false after clearing.', () { const value = 'temporary'; stringProperty[context] = value; - expect(stringProperty.exists(context), isTrue, - reason: 'Value should exist after being set'); + expect( + stringProperty.exists(context), + isTrue, + reason: 'Value should exist after being set', + ); stringProperty.clear(context); - expect(stringProperty.exists(context), isFalse, - reason: 'Value should not exist after clear'); + expect( + stringProperty.exists(context), + isFalse, + reason: 'Value should not exist after clear', + ); }); }); group( - 'Given a RequestContext and a ContextProperty for which no value is set,', - () { - test( + 'Given a RequestContext and a ContextProperty for which no value is set,', + () { + test( 'when the property has a debug name and the value is accessed using [], ' 'then a StateError is thrown with a message containing the debug name.', () { - final property = ContextProperty('debugNameProperty'); - final context = _createContextInstance('errorCtxDebug'); - expect( - () => property[context], - throwsA(isA().having( - (final e) => e.message, - 'message', - contains( - 'ContextProperty value not found. Property: debugNameProperty.'), - )), + final property = ContextProperty('debugNameProperty'); + final context = _createContextInstance('errorCtxDebug'); + expect( + () => property[context], + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains( + 'ContextProperty value not found. Property: debugNameProperty.', + ), + ), + ), + ); + }, ); - }); - test( + test( 'when the property (e.g., for int type) has no debug name and the value is accessed using [], ' 'then a StateError is thrown with a message containing the type name.', () { - final property = ContextProperty(); // No debug name - final context = _createContextInstance('errorCtxType'); - expect( - () => property[context], - throwsA(isA().having( - (final e) => e.message, - 'message', - contains('ContextProperty value not found. Property: int.'), - )), + final property = ContextProperty(); // No debug name + final context = _createContextInstance('errorCtxType'); + expect( + () => property[context], + throwsA( + isA().having( + (final e) => e.message, + 'message', + contains('ContextProperty value not found. Property: int.'), + ), + ), + ); + }, ); - }); - }); + }, + ); group('Given a ContextProperty and two RequestContexts,', () { late ContextProperty stringProperty; @@ -140,97 +150,104 @@ void main() { }); test( - 'when different values are set for each context using the same property, ' - 'then retrieving values for each context returns their respective, isolated values.', - () { - const value1 = 'value for context1'; - const value2 = 'value for context2'; + 'when different values are set for each context using the same property, ' + 'then retrieving values for each context returns their respective, isolated values.', + () { + const value1 = 'value for context1'; + const value2 = 'value for context2'; - stringProperty[context1] = value1; - stringProperty[context2] = value2; + stringProperty[context1] = value1; + stringProperty[context2] = value2; - expect(stringProperty[context1], value1); - expect(stringProperty[context2], value2); - expect(stringProperty.getOrNull(context1), value1); - expect(stringProperty.getOrNull(context2), value2); - }); + expect(stringProperty[context1], value1); + expect(stringProperty[context2], value2); + expect(stringProperty.getOrNull(context1), value1); + expect(stringProperty.getOrNull(context2), value2); + }, + ); test( - 'when values are set for both contexts and clear is called for the first context, ' - 'then only the first context value is removed and the second remains unaffected.', - () { - stringProperty[context1] = 'value1'; - stringProperty[context2] = 'value2'; + 'when values are set for both contexts and clear is called for the first context, ' + 'then only the first context value is removed and the second remains unaffected.', + () { + stringProperty[context1] = 'value1'; + stringProperty[context2] = 'value2'; - stringProperty.clear(context1); + stringProperty.clear(context1); - expect(stringProperty.exists(context1), isFalse); - expect(stringProperty.exists(context2), isTrue); - expect(stringProperty[context2], 'value2'); - }); + expect(stringProperty.exists(context1), isFalse); + expect(stringProperty.exists(context2), isTrue); + expect(stringProperty[context2], 'value2'); + }, + ); }); group( - 'Given a single RequestContext and multiple different ContextProperty instances,', - () { - test( + 'Given a single RequestContext and multiple different ContextProperty instances,', + () { + test( 'when different values are set for the same context using these different properties, ' 'then retrieving values using each property returns its respective, isolated value.', () { - final context = _createContextInstance('multiPropCtx'); - final stringProperty = ContextProperty('testStringProperty'); - final anotherStringProperty = ContextProperty('anotherString'); - final intProperty = ContextProperty(); // No debug name - - const valStrProp = 'value for stringProperty'; - const valAnotherStrProp = 'value for anotherStringProperty'; - const valIntProp = 123; - - stringProperty[context] = valStrProp; - anotherStringProperty[context] = valAnotherStrProp; - intProperty[context] = valIntProp; - - expect(stringProperty[context], valStrProp); - expect(anotherStringProperty[context], valAnotherStrProp); - expect(intProperty[context], valIntProp); - }); - }); + final context = _createContextInstance('multiPropCtx'); + final stringProperty = ContextProperty('testStringProperty'); + final anotherStringProperty = ContextProperty( + 'anotherString', + ); + final intProperty = ContextProperty(); // No debug name + + const valStrProp = 'value for stringProperty'; + const valAnotherStrProp = 'value for anotherStringProperty'; + const valIntProp = 123; + + stringProperty[context] = valStrProp; + anotherStringProperty[context] = valAnotherStrProp; + intProperty[context] = valIntProp; + + expect(stringProperty[context], valStrProp); + expect(anotherStringProperty[context], valAnotherStrProp); + expect(intProperty[context], valIntProp); + }, + ); + }, + ); - group( - 'Given a RequestContext and a ContextProperty for a specific data type,', - () { + group('Given a RequestContext and a ContextProperty for a specific data type,', () { test( - 'when the type is int, an int value is set, retrieved, its existence checked, and then cleared, ' - 'then all operations behave as expected.', () { - final numberProperty = ContextProperty('numberProperty'); - final context = _createContextInstance('intTypeCtx'); - const value = 42; - - numberProperty[context] = value; - expect(numberProperty[context], value); - expect(numberProperty.getOrNull(context), value); - expect(numberProperty.exists(context), isTrue); - - numberProperty.clear(context); - expect(numberProperty.exists(context), isFalse); - }); + 'when the type is int, an int value is set, retrieved, its existence checked, and then cleared, ' + 'then all operations behave as expected.', + () { + final numberProperty = ContextProperty('numberProperty'); + final context = _createContextInstance('intTypeCtx'); + const value = 42; - test( - 'when the type is a custom User object, a User instance is set, retrieved, its existence checked, and then cleared, ' - 'then all operations behave as expected and the same instance is retrieved.', - () { - final userProperty = ContextProperty('userProperty'); - final context = _createContextInstance('userTypeCtx'); - final user = User('Test User', 30); + numberProperty[context] = value; + expect(numberProperty[context], value); + expect(numberProperty.getOrNull(context), value); + expect(numberProperty.exists(context), isTrue); - userProperty[context] = user; - expect(userProperty[context], same(user)); - expect(userProperty.getOrNull(context), same(user)); - expect(userProperty.exists(context), isTrue); + numberProperty.clear(context); + expect(numberProperty.exists(context), isFalse); + }, + ); - userProperty.clear(context); - expect(userProperty.exists(context), isFalse); - }); + test( + 'when the type is a custom User object, a User instance is set, retrieved, its existence checked, and then cleared, ' + 'then all operations behave as expected and the same instance is retrieved.', + () { + final userProperty = ContextProperty('userProperty'); + final context = _createContextInstance('userTypeCtx'); + final user = User('Test User', 30); + + userProperty[context] = user; + expect(userProperty[context], same(user)); + expect(userProperty.getOrNull(context), same(user)); + expect(userProperty.exists(context), isTrue); + + userProperty.clear(context); + expect(userProperty.exists(context), isFalse); + }, + ); }); } diff --git a/test/static/alternative_root_test.dart b/test/static/alternative_root_test.dart index 139e3067..52a320ec 100644 --- a/test/static/alternative_root_test.dart +++ b/test/static/alternative_root_test.dart @@ -12,73 +12,105 @@ void main() { await d.file('root.txt', 'root txt').create(); await d.dir('files', [ d.file('test.txt', 'test txt content'), - d.file('with space.txt', 'with space content') + d.file('with space.txt', 'with space content'), ]).create(); }); - test('Given a root file when accessed then it returns the file content', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + test( + 'Given a root file when accessed then it returns the file content', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - final response = await makeRequest( - handler, - '/static/root.txt', - handlerPath: 'static', - ); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 8); - expect(response.readAsString(), completion('root txt')); - }); + final response = await makeRequest( + handler, + '/static/root.txt', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 8); + expect(response.readAsString(), completion('root txt')); + }, + ); test( - 'Given a root file with space when accessed then it returns the file content', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + 'Given a root file with space when accessed then it returns the file content', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - final response = await makeRequest( - handler, '/static/files/with%20space.txt', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 18); - expect(response.readAsString(), completion('with space content')); - }); + final response = await makeRequest( + handler, + '/static/files/with%20space.txt', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 18); + expect(response.readAsString(), completion('with space content')); + }, + ); test( - 'Given a root file with unencoded space when accessed then it returns the file content', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + 'Given a root file with unencoded space when accessed then it returns the file content', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - final response = await makeRequest( - handler, '/static/files/with%20space.txt', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 18); - expect(response.readAsString(), completion('with space content')); - }); + final response = await makeRequest( + handler, + '/static/files/with%20space.txt', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 18); + expect(response.readAsString(), completion('with space content')); + }, + ); test( - 'Given a file under directory when accessed then it returns the file content', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + 'Given a file under directory when accessed then it returns the file content', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - final response = await makeRequest(handler, '/static/files/test.txt', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 16); - expect(response.readAsString(), completion('test txt content')); - }); + final response = await makeRequest( + handler, + '/static/files/test.txt', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 16); + expect(response.readAsString(), completion('test txt content')); + }, + ); - test('Given a non-existent file when accessed then it returns a 404 status', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + test( + 'Given a non-existent file when accessed then it returns a 404 status', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - final response = await makeRequest(handler, '/static/not_here.txt', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.notFound); - }); + final response = await makeRequest( + handler, + '/static/not_here.txt', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.notFound); + }, + ); } diff --git a/test/static/basic_file_test.dart b/test/static/basic_file_test.dart index ab27005c..8004e89a 100644 --- a/test/static/basic_file_test.dart +++ b/test/static/basic_file_test.dart @@ -30,122 +30,166 @@ void main() { d.file('test.txt', 'test txt content'), d.file('with space.txt', 'with space content'), d.file('header_bytes_test_image', base64Decode(pngBytesContent)), - d.file('header_bytes_test_webp', base64Decode(webpBytesContent)) + d.file('header_bytes_test_webp', base64Decode(webpBytesContent)), ]).create(); }); - test('Given a root file when accessed then it returns the file content', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; - - final response = await makeRequest(handler, '/root.txt'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 8); - expect(response.readAsString(), completion('root txt')); - }); - test( - 'Given a HEAD request when made then it returns the correct headers without body', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; - - final response = - await makeRequest(handler, '/root.txt', method: Method.head); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 8); - expect(await response.readAsString(), isEmpty); - }); + 'Given a root file when accessed then it returns the file content', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - test( - 'Given a root file with space when accessed then it returns the file content', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; - - final response = await makeRequest(handler, '/files/with%20space.txt'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 18); - expect(response.readAsString(), completion('with space content')); - }); + final response = await makeRequest(handler, '/root.txt'); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 8); + expect(response.readAsString(), completion('root txt')); + }, + ); test( - 'Given a root file with unencoded space when accessed then it returns the file content', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + 'Given a HEAD request when made then it returns the correct headers without body', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - final response = await makeRequest(handler, '/files/with space.txt'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 18); - expect(response.readAsString(), completion('with space content')); - }); + final response = await makeRequest( + handler, + '/root.txt', + method: Method.head, + ); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 8); + expect(await response.readAsString(), isEmpty); + }, + ); test( - 'Given a file under directory when accessed then it returns the file content', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + 'Given a root file with space when accessed then it returns the file content', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; + + final response = await makeRequest(handler, '/files/with%20space.txt'); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 18); + expect(response.readAsString(), completion('with space content')); + }, + ); - final response = await makeRequest(handler, '/files/test.txt'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 16); - expect(response.readAsString(), completion('test txt content')); - }); + test( + 'Given a root file with unencoded space when accessed then it returns the file content', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; + + final response = await makeRequest(handler, '/files/with space.txt'); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 18); + expect(response.readAsString(), completion('with space content')); + }, + ); - test('Given a non-existent file when accessed then it returns a 404 status', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + test( + 'Given a file under directory when accessed then it returns the file content', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; + + final response = await makeRequest(handler, '/files/test.txt'); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 16); + expect(response.readAsString(), completion('test txt content')); + }, + ); - final response = await makeRequest(handler, '/not_here.txt'); - expect(response.statusCode, HttpStatus.notFound); - }); + test( + 'Given a non-existent file when accessed then it returns a 404 status', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; + + final response = await makeRequest(handler, '/not_here.txt'); + expect(response.statusCode, HttpStatus.notFound); + }, + ); test( - 'Given a file when accessed then it returns the correct last modified date', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + 'Given a file when accessed then it returns the correct last modified date', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - final rootPath = p.join(d.sandbox, 'root.txt'); - final modified = File(rootPath).statSync().modified.toUtc(); + final rootPath = p.join(d.sandbox, 'root.txt'); + final modified = File(rootPath).statSync().modified.toUtc(); - final response = await makeRequest(handler, '/root.txt'); - expect( - response.headers.lastModified, - atSameTimeToSecond(modified), - ); - }); + final response = await makeRequest(handler, '/root.txt'); + expect(response.headers.lastModified, atSameTimeToSecond(modified)); + }, + ); group('Given if modified since', () { - test('when it is the same as last modified then it returns not modified', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + test( + 'when it is the same as last modified then it returns not modified', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - final rootPath = p.join(d.sandbox, 'root.txt'); - final modified = File(rootPath).statSync().modified.toUtc(); + final rootPath = p.join(d.sandbox, 'root.txt'); + final modified = File(rootPath).statSync().modified.toUtc(); - final headers = - Headers.build((final mh) => mh.ifModifiedSince = modified); + final headers = Headers.build( + (final mh) => mh.ifModifiedSince = modified, + ); - final response = - await makeRequest(handler, '/root.txt', headers: headers); - expect(response.statusCode, HttpStatus.notModified); - expect(response.body.contentLength, 0); - }); + final response = await makeRequest( + handler, + '/root.txt', + headers: headers, + ); + expect(response.statusCode, HttpStatus.notModified); + expect(response.body.contentLength, 0); + }, + ); test('when it is before last modified then it returns the file', () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; final rootPath = p.join(d.sandbox, 'root.txt'); final modified = File(rootPath).statSync().modified.toUtc(); - final headers = Headers.build((final mh) => - mh.ifModifiedSince = modified.subtract(const Duration(seconds: 1))); + final headers = Headers.build( + (final mh) => + mh.ifModifiedSince = modified.subtract(const Duration(seconds: 1)), + ); final response = await makeRequest( handler, @@ -153,66 +197,77 @@ void main() { headers: headers, ); expect(response.statusCode, HttpStatus.ok); - expect( - response.headers.lastModified, - atSameTimeToSecond(modified), - ); + expect(response.headers.lastModified, atSameTimeToSecond(modified)); }); - test('when it is after last modified then it returns not modified', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + test( + 'when it is after last modified then it returns not modified', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; - final rootPath = p.join(d.sandbox, 'root.txt'); - final modified = File(rootPath).statSync().modified.toUtc(); + final rootPath = p.join(d.sandbox, 'root.txt'); + final modified = File(rootPath).statSync().modified.toUtc(); - final headers = - Headers.build((final mh) => mh.ifModifiedSince = modified); + final headers = Headers.build( + (final mh) => mh.ifModifiedSince = modified, + ); - final response = await makeRequest( - handler, - '/root.txt', - headers: headers, - ); + final response = await makeRequest( + handler, + '/root.txt', + headers: headers, + ); - expect(response.statusCode, HttpStatus.notModified); - expect(response.body.contentLength, 0); - }); + expect(response.statusCode, HttpStatus.notModified); + expect(response.body.contentLength, 0); + }, + ); test( - 'when file is modified after the request then it returns the updated file', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; - final rootPath = p.join(d.sandbox, 'root.txt'); + 'when file is modified after the request then it returns the updated file', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; + final rootPath = p.join(d.sandbox, 'root.txt'); - final response1 = await makeRequest(handler, '/root.txt'); - final originalModificationDate = response1.headers.lastModified!; + final response1 = await makeRequest(handler, '/root.txt'); + final originalModificationDate = response1.headers.lastModified!; - await Future.delayed(const Duration(seconds: 2)); - File(rootPath).writeAsStringSync('updated root txt'); + await Future.delayed(const Duration(seconds: 2)); + File(rootPath).writeAsStringSync('updated root txt'); - final headers = Headers.build( - (final mh) => mh.ifModifiedSince = originalModificationDate); + final headers = Headers.build( + (final mh) => mh.ifModifiedSince = originalModificationDate, + ); - final response2 = await makeRequest( - handler, - '/root.txt', - headers: headers, - ); - expect(response2.statusCode, HttpStatus.ok); - expect( - response2.headers.lastModified!.millisecondsSinceEpoch, - greaterThan(originalModificationDate.millisecondsSinceEpoch), - ); - }); + final response2 = await makeRequest( + handler, + '/root.txt', + headers: headers, + ); + expect(response2.statusCode, HttpStatus.ok); + expect( + response2.headers.lastModified!.millisecondsSinceEpoch, + greaterThan(originalModificationDate.millisecondsSinceEpoch), + ); + }, + ); }); group('Given content type', () { test('when accessing root.txt then it should be text/plain', () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest(handler, '/root.txt'); expect(response.mimeType?.primaryType, 'text'); @@ -220,58 +275,76 @@ void main() { }); test('when accessing index.html then it should be text/html', () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest(handler, '/index.html'); expect(response.mimeType?.primaryType, 'text'); expect(response.mimeType?.subType, 'html'); }); - test( - 'when accessing random.unknown, ' + test('when accessing random.unknown, ' 'then it should be application/octet-stream', () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest(handler, '/random.unknown'); expect(response.mimeType, MimeType.octetStream); }); - test('when accessing header_bytes_test_image then it should be image/png', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; - - final response = - await makeRequest(handler, '/files/header_bytes_test_image'); - expect(response.mimeType?.primaryType, 'image'); - expect(response.mimeType?.subType, 'png'); - }); + test( + 'when accessing header_bytes_test_image then it should be image/png', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; + + final response = await makeRequest( + handler, + '/files/header_bytes_test_image', + ); + expect(response.mimeType?.primaryType, 'image'); + expect(response.mimeType?.subType, 'png'); + }, + ); - test('when accessing header_bytes_test_webp then it should be image/webp', - () async { - final resolver = mime.MimeTypeResolver() - ..addMagicNumber( - [ - 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, // - 0x00, 0x00, 0x57, 0x45, 0x42, 0x50 - ], - 'image/webp', - mask: [ - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, // - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF - ], + test( + 'when accessing header_bytes_test_webp then it should be image/webp', + () async { + final resolver = + mime.MimeTypeResolver()..addMagicNumber( + [ + 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, // + 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, + ], + 'image/webp', + mask: [ + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, // + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + ], + ); + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + /* useHeaderBytesForContentType: true, */ mimeResolver: resolver, + ).asHandler; + + final response = await makeRequest( + handler, + '/files/header_bytes_test_webp', ); - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null, - /* useHeaderBytesForContentType: true, */ mimeResolver: resolver) - .asHandler; - - final response = - await makeRequest(handler, '/files/header_bytes_test_webp'); - expect(response.mimeType?.primaryType, 'image'); - expect(response.mimeType?.subType, 'webp'); - }); + expect(response.mimeType?.primaryType, 'image'); + expect(response.mimeType?.subType, 'webp'); + }, + ); }); } diff --git a/test/static/cache_busting_config_test.dart b/test/static/cache_busting_config_test.dart index cb6ba193..c10603bc 100644 --- a/test/static/cache_busting_config_test.dart +++ b/test/static/cache_busting_config_test.dart @@ -7,119 +7,126 @@ import 'package:test_descriptor/test_descriptor.dart' as d; void main() { test( - 'Given mountPrefix not starting with "/" when creating CacheBustingConfig then it throws ArgumentError', - () async { - final staticRoot = Directory(p.join(d.sandbox, 'static')); - expect( - () => CacheBustingConfig( - mountPrefix: 'static', - fileSystemRoot: staticRoot, - ), - throwsA(isA()), - ); - }); + 'Given mountPrefix not starting with "/" when creating CacheBustingConfig then it throws ArgumentError', + () async { + final staticRoot = Directory(p.join(d.sandbox, 'static')); + expect( + () => CacheBustingConfig( + mountPrefix: 'static', + fileSystemRoot: staticRoot, + ), + throwsA(isA()), + ); + }, + ); test( - 'Given empty separator when creating CacheBustingConfig then it throws ArgumentError', - () async { - final staticRoot = Directory(p.join(d.sandbox, 'static')); - expect( - () => CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '', - ), - throwsA(isA()), - ); - }); + 'Given empty separator when creating CacheBustingConfig then it throws ArgumentError', + () async { + final staticRoot = Directory(p.join(d.sandbox, 'static')); + expect( + () => CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '', + ), + throwsA(isA()), + ); + }, + ); test( - 'Given "/" separator when creating CacheBustingConfig then it throws ArgumentError', - () async { - final staticRoot = Directory(p.join(d.sandbox, 'static')); - expect( - () => CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '/', - ), - throwsA(isA()), - ); - }); + 'Given "/" separator when creating CacheBustingConfig then it throws ArgumentError', + () async { + final staticRoot = Directory(p.join(d.sandbox, 'static')); + expect( + () => CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '/', + ), + throwsA(isA()), + ); + }, + ); test( - 'Given separator starting with "/" when creating CacheBustingConfig then it throws ArgumentError', - () async { - final staticRoot = Directory(p.join(d.sandbox, 'static')); - expect( - () => CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '/@', - ), - throwsA(isA()), - ); - }); + 'Given separator starting with "/" when creating CacheBustingConfig then it throws ArgumentError', + () async { + final staticRoot = Directory(p.join(d.sandbox, 'static')); + expect( + () => CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '/@', + ), + throwsA(isA()), + ); + }, + ); test( - 'Given separator ending with "/" when creating CacheBustingConfig then it throws ArgumentError', - () async { - final staticRoot = Directory(p.join(d.sandbox, 'static')); - expect( - () => CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@/', - ), - throwsA(isA()), - ); - }); + 'Given separator ending with "/" when creating CacheBustingConfig then it throws ArgumentError', + () async { + final staticRoot = Directory(p.join(d.sandbox, 'static')); + expect( + () => CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@/', + ), + throwsA(isA()), + ); + }, + ); test( - 'Given separator containing "/" when creating CacheBustingConfig then it throws ArgumentError', - () async { - final staticRoot = Directory(p.join(d.sandbox, 'static')); - expect( - () => CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@/@', - ), - throwsA(isA()), - ); - }); + 'Given separator containing "/" when creating CacheBustingConfig then it throws ArgumentError', + () async { + final staticRoot = Directory(p.join(d.sandbox, 'static')); + expect( + () => CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@/@', + ), + throwsA(isA()), + ); + }, + ); test( - 'Given no directory at staticRoot when creating CacheBustingConfig then it throws ArgumentError', - () { - final staticRoot = Directory(p.join(d.sandbox, 'static')); - expect( - () => CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ), - throwsA(isA()), - ); - }); + 'Given no directory at staticRoot when creating CacheBustingConfig then it throws ArgumentError', + () { + final staticRoot = Directory(p.join(d.sandbox, 'static')); + expect( + () => CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@', + ), + throwsA(isA()), + ); + }, + ); test( - 'Given file at staticRoot when creating CacheBustingConfig then it throws ArgumentError', - () async { - await d.file('static', 'content').create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - expect( - () => CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ), - throwsA(isA()), - ); - }); + 'Given file at staticRoot when creating CacheBustingConfig then it throws ArgumentError', + () async { + await d.file('static', 'content').create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + expect( + () => CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@', + ), + throwsA(isA()), + ); + }, + ); - group('Given CacheBustingConfig configured for a directory without files', - () { + group('Given CacheBustingConfig configured for a directory without files', () { late CacheBustingConfig cfg; setUp(() async { await d.dir('static', []).create(); @@ -131,44 +138,48 @@ void main() { }); test( - 'when assetPath is called for a missing file then it throws PathNotFoundException', - () async { - expect( - cfg.assetPath('/static/does-not-exist.txt'), - throwsA(isA()), - ); - }); + 'when assetPath is called for a missing file then it throws PathNotFoundException', + () async { + expect( + cfg.assetPath('/static/does-not-exist.txt'), + throwsA(isA()), + ); + }, + ); test( - 'when tryAssetPath is called for a missing file then it returns the original path', - () async { - const original = '/static/does-not-exist.txt'; - final result = await cfg.tryAssetPath(original); - expect(result, original); - }); + 'when tryAssetPath is called for a missing file then it returns the original path', + () async { + const original = '/static/does-not-exist.txt'; + final result = await cfg.tryAssetPath(original); + expect(result, original); + }, + ); }); group( - 'Given CacheBustingConfig without explicit separator configured for a directory with files', - () { - late CacheBustingConfig cfg; - setUp(() async { - await d.dir('static', [d.file('logo.png', 'png-bytes')]).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - cfg = CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - ); - }); - - test( + 'Given CacheBustingConfig without explicit separator configured for a directory with files', + () { + late CacheBustingConfig cfg; + setUp(() async { + await d.dir('static', [d.file('logo.png', 'png-bytes')]).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + cfg = CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + ); + }); + + test( 'when assetPath is called for existing file then default cache busting separator is "@"', () async { - const original = '/static/logo.png'; - final busted = await cfg.assetPath(original); - expect(busted, contains('@')); - }); - }); + const original = '/static/logo.png'; + final busted = await cfg.assetPath(original); + expect(busted, contains('@')); + }, + ); + }, + ); group('Given CacheBustingConfig configured for a directory with files', () { late CacheBustingConfig cfg; @@ -183,42 +194,46 @@ void main() { }); test( - 'when assetPath is called for an existing file then it returns a cache busted path', - () async { - const original = '/static/logo.png'; - final busted = await cfg.assetPath(original); - expect(busted, startsWith('/static/logo@')); - }); + 'when assetPath is called for an existing file then it returns a cache busted path', + () async { + const original = '/static/logo.png'; + final busted = await cfg.assetPath(original); + expect(busted, startsWith('/static/logo@')); + }, + ); test( - 'when tryAssetPath is called for an existing file then it returns a cache busted path', - () async { - const original = '/static/logo.png'; - final busted = await cfg.tryAssetPath(original); - expect(busted, startsWith('/static/logo@')); - }); + 'when tryAssetPath is called for an existing file then it returns a cache busted path', + () async { + const original = '/static/logo.png'; + final busted = await cfg.tryAssetPath(original); + expect(busted, startsWith('/static/logo@')); + }, + ); test( - 'when assetPath is called with an absolute path segment after mount then argument error is thrown', - () async { - expect( - cfg.assetPath('/static//logo.png'), - throwsA(isA()), - ); - }); + 'when assetPath is called with an absolute path segment after mount then argument error is thrown', + () async { + expect( + cfg.assetPath('/static//logo.png'), + throwsA(isA()), + ); + }, + ); test( - 'when tryAssetPath is called with an absolute path segment after mount then it returns the original path', - () async { - final staticRoot = Directory(p.join(d.sandbox, 'static')); - final cfg = CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - ); - - const original = '/static//logo.png'; - expect(await cfg.tryAssetPath(original), original); - }); + 'when tryAssetPath is called with an absolute path segment after mount then it returns the original path', + () async { + final staticRoot = Directory(p.join(d.sandbox, 'static')); + final cfg = CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + ); + + const original = '/static//logo.png'; + expect(await cfg.tryAssetPath(original), original); + }, + ); }); group('Given files outside of CacheBustingConfig fileSystemRoot', () { @@ -234,44 +249,48 @@ void main() { }); test( - 'when assetPath is called for a path that traverses outside of the mount prefix then it throws ArgumentError', - () async { - expect( - cfg.assetPath('/static/../secret.txt'), - throwsA(isA()), - ); - }); + 'when assetPath is called for a path that traverses outside of the mount prefix then it throws ArgumentError', + () async { + expect( + cfg.assetPath('/static/../secret.txt'), + throwsA(isA()), + ); + }, + ); test( - 'when assetPath is called for a path outside of the mount prefix then it returns it unchanged', - () async { - const outside = '/secret.txt'; - expect(await cfg.assetPath(outside), outside); - }); + 'when assetPath is called for a path outside of the mount prefix then it returns it unchanged', + () async { + const outside = '/secret.txt'; + expect(await cfg.assetPath(outside), outside); + }, + ); test( - 'when tryAssetPath is called for a path outside of mount prefix then returns it unchanged', - () async { - const outside = '/secret.txt'; - expect(await cfg.tryAssetPath(outside), outside); - }); + 'when tryAssetPath is called for a path outside of mount prefix then returns it unchanged', + () async { + const outside = '/secret.txt'; + expect(await cfg.tryAssetPath(outside), outside); + }, + ); }); test( - 'Given file without extension in CacheBustingConfig directory when calling assetPath then it returns a cache busting path', - () async { - await d.dir('static', [d.file('logo', 'content-noext')]).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - final cfg = CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ); + 'Given file without extension in CacheBustingConfig directory when calling assetPath then it returns a cache busting path', + () async { + await d.dir('static', [d.file('logo', 'content-noext')]).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + final cfg = CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@', + ); - const original = '/static/logo'; - final busted = await cfg.assetPath(original); - expect(busted, startsWith('/static/logo@')); - }); + const original = '/static/logo'; + final busted = await cfg.assetPath(original); + expect(busted, startsWith('/static/logo@')); + }, + ); group('Given a CacheBustingConfig with custom separator', () { late CacheBustingConfig cfg; @@ -292,109 +311,119 @@ void main() { }); test( - 'when tryStripHashFromFilename is called with busted path then hash is stripped from filename', - () async { - expect(cfg.tryStripHashFromFilename('logo--abc123.png'), 'logo.png'); - }); - }); - - test( - 'Given a CacheBustingConfig serving a directory where the directory name contains the separator when calling assetPath then only the filename is affected', + 'when tryStripHashFromFilename is called with busted path then hash is stripped from filename', () async { - await d.dir(p.join('static', 'img@foo'), [ - d.file('logo.png', 'dir-at-bytes'), - ]).create(); - - final staticRoot = Directory(p.join(d.sandbox, 'static')); - final cfg = CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', + expect(cfg.tryStripHashFromFilename('logo--abc123.png'), 'logo.png'); + }, ); - - const original = '/static/img@foo/logo.png'; - final busted = await cfg.assetPath(original); - expect(busted, startsWith('/static/img@foo/logo@')); }); test( - 'Given a CacheBustingConfig serving a file starting with the separator when calling assetPath then cache busting path is created correctly', - () async { - await d.dir('static', [d.file('@logo.png', 'at-logo')]).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - final cfg = CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ); - - const original = '/static/@logo.png'; - final busted = await cfg.assetPath(original); - expect(busted, startsWith('/static/@logo@')); - }); - - group('Given a CacheBustingConfig serving a symlink that escapes the root', - () { - late CacheBustingConfig cfg; - setUp(() async { - await d.file('outside.txt', 'outside').create(); - await d.dir('static').create(); - final outsidePath = p.join(d.sandbox, 'outside.txt'); - final linkPath = p.join(d.sandbox, 'static', 'escape.txt'); - Link(linkPath).createSync(outsidePath); + 'Given a CacheBustingConfig serving a directory where the directory name contains the separator when calling assetPath then only the filename is affected', + () async { + await d.dir(p.join('static', 'img@foo'), [ + d.file('logo.png', 'dir-at-bytes'), + ]).create(); final staticRoot = Directory(p.join(d.sandbox, 'static')); - cfg = CacheBustingConfig( + final cfg = CacheBustingConfig( mountPrefix: '/static', fileSystemRoot: staticRoot, + separator: '@', ); - }); - test('when calling assetPath then it throws ArgumentError', () async { - expect( - cfg.assetPath('/static/escape.txt'), - throwsA(isA()), - ); - }); - test('when calling tryAssetPath then asset path is returned unchanged', - () async { - expect( - await cfg.tryAssetPath('/static/escape.txt'), '/static/escape.txt'); - }); - }); + const original = '/static/img@foo/logo.png'; + final busted = await cfg.assetPath(original); + expect(busted, startsWith('/static/img@foo/logo@')); + }, + ); - group( - 'Given a CacheBustingConfig with a mountPrefix that does not match fileSystemRoot', - () { - late CacheBustingConfig cfg; - setUp(() async { - await d.dir('static', [ - d.file('logo.png', 'png-bytes'), - ]).create(); + test( + 'Given a CacheBustingConfig serving a file starting with the separator when calling assetPath then cache busting path is created correctly', + () async { + await d.dir('static', [d.file('@logo.png', 'at-logo')]).create(); final staticRoot = Directory(p.join(d.sandbox, 'static')); - cfg = CacheBustingConfig( - mountPrefix: '/web', + final cfg = CacheBustingConfig( + mountPrefix: '/static', fileSystemRoot: staticRoot, separator: '@', ); - }); - test( + const original = '/static/@logo.png'; + final busted = await cfg.assetPath(original); + expect(busted, startsWith('/static/@logo@')); + }, + ); + + group( + 'Given a CacheBustingConfig serving a symlink that escapes the root', + () { + late CacheBustingConfig cfg; + setUp(() async { + await d.file('outside.txt', 'outside').create(); + await d.dir('static').create(); + final outsidePath = p.join(d.sandbox, 'outside.txt'); + final linkPath = p.join(d.sandbox, 'static', 'escape.txt'); + Link(linkPath).createSync(outsidePath); + + final staticRoot = Directory(p.join(d.sandbox, 'static')); + cfg = CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + ); + }); + test('when calling assetPath then it throws ArgumentError', () async { + expect( + cfg.assetPath('/static/escape.txt'), + throwsA(isA()), + ); + }); + + test( + 'when calling tryAssetPath then asset path is returned unchanged', + () async { + expect( + await cfg.tryAssetPath('/static/escape.txt'), + '/static/escape.txt', + ); + }, + ); + }, + ); + + group( + 'Given a CacheBustingConfig with a mountPrefix that does not match fileSystemRoot', + () { + late CacheBustingConfig cfg; + setUp(() async { + await d.dir('static', [d.file('logo.png', 'png-bytes')]).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + cfg = CacheBustingConfig( + mountPrefix: '/web', + fileSystemRoot: staticRoot, + separator: '@', + ); + }); + + test( 'when assetPath is called for an existing file then it returns a cache busted path', () async { - const original = '/web/logo.png'; - final busted = await cfg.assetPath(original); - expect(busted, startsWith('/web/logo@')); - }); + const original = '/web/logo.png'; + final busted = await cfg.assetPath(original); + expect(busted, startsWith('/web/logo@')); + }, + ); - test( + test( 'when assetPath is called for file using fileSystemRoot instead of mountPrefix as base then it returns path unchanged', () async { - const original = '/static/logo.png'; - final busted = await cfg.assetPath(original); - expect(busted, equals('/static/logo.png')); - }); - }); + const original = '/static/logo.png'; + final busted = await cfg.assetPath(original); + expect(busted, equals('/static/logo.png')); + }, + ); + }, + ); group('Given cache busting config', () { late CacheBustingConfig cfg; @@ -409,33 +438,38 @@ void main() { }); test( - 'when tryStripHashFromFilename is filename equals to the separator then same path is returned', - () async { - expect(cfg.tryStripHashFromFilename('@'), '@'); - }); + 'when tryStripHashFromFilename is filename equals to the separator then same path is returned', + () async { + expect(cfg.tryStripHashFromFilename('@'), '@'); + }, + ); test( - 'when tryStripHashFromFilename is called with non busted filename then same filename is returned', - () async { - expect(cfg.tryStripHashFromFilename('logo.png'), 'logo.png'); - }); + 'when tryStripHashFromFilename is called with non busted filename then same filename is returned', + () async { + expect(cfg.tryStripHashFromFilename('logo.png'), 'logo.png'); + }, + ); test( - 'when tryStripHashFromFilename is called with busted filename then hash is stripped from filename', - () async { - expect(cfg.tryStripHashFromFilename('logo@abc123.png'), 'logo.png'); - }); + 'when tryStripHashFromFilename is called with busted filename then hash is stripped from filename', + () async { + expect(cfg.tryStripHashFromFilename('logo@abc123.png'), 'logo.png'); + }, + ); test( - 'when tryStripHashFromFilename is called with busted filename that has no extension then it strips the hash and keeps no extension', - () async { - expect(cfg.tryStripHashFromFilename('logo@abc123'), 'logo'); - }); + 'when tryStripHashFromFilename is called with busted filename that has no extension then it strips the hash and keeps no extension', + () async { + expect(cfg.tryStripHashFromFilename('logo@abc123'), 'logo'); + }, + ); test( - 'when tryStripHashFromFilename is called with busted filename starting with separator then only trailing hash is stripped', - () async { - expect(cfg.tryStripHashFromFilename('@logo@abc123.png'), '@logo.png'); - }); + 'when tryStripHashFromFilename is called with busted filename starting with separator then only trailing hash is stripped', + () async { + expect(cfg.tryStripHashFromFilename('@logo@abc123.png'), '@logo.png'); + }, + ); }); } diff --git a/test/static/cache_busting_static_handler_test.dart b/test/static/cache_busting_static_handler_test.dart index bb23991f..bed9ab74 100644 --- a/test/static/cache_busting_static_handler_test.dart +++ b/test/static/cache_busting_static_handler_test.dart @@ -9,81 +9,98 @@ import 'test_util.dart'; void main() { group( - 'Given a static asset served through a root-mounted cache busting static file handler', - () { - late Handler handler; - setUp(() async { - await d.dir('static', [d.file('logo.png', 'png-bytes')]).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - handler = const Pipeline().addHandler(StaticHandler.directory( - staticRoot, - cacheControl: (final _, final __) => null, - cacheBustingConfig: CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ), - ).asHandler); - }); + 'Given a static asset served through a root-mounted cache busting static file handler', + () { + late Handler handler; + setUp(() async { + await d.dir('static', [d.file('logo.png', 'png-bytes')]).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + handler = const Pipeline().addHandler( + StaticHandler.directory( + staticRoot, + cacheControl: (_, _) => null, + cacheBustingConfig: CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@', + ), + ).asHandler, + ); + }); - test('when requesting asset with a non-busted URL then it serves the asset', + test( + 'when requesting asset with a non-busted URL then it serves the asset', () async { - final response = - await makeRequest(handler, '/static/logo.png', handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); + final response = await makeRequest( + handler, + '/static/logo.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); - test( + test( 'when requesting asset with a cache busted URL then it serves the asset', () async { - final response = await makeRequest(handler, '/static/logo@abc.png', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); - }); + final response = await makeRequest( + handler, + '/static/logo@abc.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); + }, + ); group( - 'Given a static asset served through a router-mounted cache busting static handler', - () { - late Handler handler; - setUp(() async { - await d.dir('static', [d.file('logo.png', 'png-bytes')]).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - final router = RelicRouter() - ..get( - '/static/**', - StaticHandler.directory( - staticRoot, - cacheControl: (final _, final __) => null, - cacheBustingConfig: CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ), - ).asHandler); + 'Given a static asset served through a router-mounted cache busting static handler', + () { + late Handler handler; + setUp(() async { + await d.dir('static', [d.file('logo.png', 'png-bytes')]).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + final router = + RelicRouter()..get( + '/static/**', + StaticHandler.directory( + staticRoot, + cacheControl: (_, _) => null, + cacheBustingConfig: CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@', + ), + ).asHandler, + ); - handler = const Pipeline() - .addMiddleware(routeWith(router)) - .addHandler(respondWith((final _) => Response.notFound())); - }); + handler = const Pipeline() + .addMiddleware(routeWith(router)) + .addHandler(respondWith((_) => Response.notFound())); + }); - test('when requesting asset with a non-busted URL then it serves the asset', + test( + 'when requesting asset with a non-busted URL then it serves the asset', () async { - final response = await makeRequest(handler, '/static/logo.png'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); + final response = await makeRequest(handler, '/static/logo.png'); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); - test( + test( 'when requesting asset with a cache busted URL then it serves the asset', () async { - final response = await makeRequest(handler, '/static/logo@abc.png'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); - }); + final response = await makeRequest(handler, '/static/logo@abc.png'); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); + }, + ); group('Given static asset served outside of cache busting mountPrefix', () { late Handler handler; @@ -91,227 +108,301 @@ void main() { setUp(() async { await d.dir('other', [d.file('logo.png', 'png-bytes')]).create(); final staticRoot = Directory(p.join(d.sandbox, 'other')); - handler = const Pipeline().addHandler(StaticHandler.directory( - staticRoot, - cacheControl: (final _, final __) => null, - cacheBustingConfig: CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ), - ).asHandler); + handler = const Pipeline().addHandler( + StaticHandler.directory( + staticRoot, + cacheControl: (_, _) => null, + cacheBustingConfig: CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@', + ), + ).asHandler, + ); }); - test('when requesting asset with a non-busted URL then it serves the asset', - () async { - final response = - await makeRequest(handler, '/other/logo.png', handlerPath: 'other'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); + test( + 'when requesting asset with a non-busted URL then it serves the asset', + () async { + final response = await makeRequest( + handler, + '/other/logo.png', + handlerPath: 'other', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); test( - 'when requesting asset with a busted URL then it still serves the asset (handler-level cache busting)', - () async { - final response = await makeRequest(handler, '/other/logo@abc.png', - handlerPath: 'other'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); + 'when requesting asset with a busted URL then it still serves the asset (handler-level cache busting)', + () async { + final response = await makeRequest( + handler, + '/other/logo@abc.png', + handlerPath: 'other', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); }); group( - 'Given a static asset with a filename starting with separator served through cache busting static file handler', - () { - late Handler handler; - setUp(() async { - await d.dir('static', [d.file('@logo.png', 'png-bytes')]).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - handler = const Pipeline().addHandler(StaticHandler.directory( - staticRoot, - cacheControl: (final _, final __) => null, - cacheBustingConfig: CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ), - ).asHandler); - }); + 'Given a static asset with a filename starting with separator served through cache busting static file handler', + () { + late Handler handler; + setUp(() async { + await d.dir('static', [d.file('@logo.png', 'png-bytes')]).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + handler = const Pipeline().addHandler( + StaticHandler.directory( + staticRoot, + cacheControl: (_, _) => null, + cacheBustingConfig: CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@', + ), + ).asHandler, + ); + }); - test('when requesting asset with a non-busted URL then it serves the asset', + test( + 'when requesting asset with a non-busted URL then it serves the asset', () async { - final response = await makeRequest(handler, '/static/@logo.png', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); + final response = await makeRequest( + handler, + '/static/@logo.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); - test( + test( 'when requesting asset with a cache busted URL then it serves the asset', () async { - final response = await makeRequest(handler, '/static/@logo@abc.png', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); - }); + final response = await makeRequest( + handler, + '/static/@logo@abc.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); + }, + ); group( - 'Given a static asset without extension served through cache busting static file handler', - () { - late Handler handler; - setUp(() async { - await d.dir('static', [d.file('logo', 'file-contents')]).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - handler = const Pipeline().addHandler(StaticHandler.directory( - staticRoot, - cacheControl: (final _, final __) => null, - cacheBustingConfig: CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ), - ).asHandler); - }); + 'Given a static asset without extension served through cache busting static file handler', + () { + late Handler handler; + setUp(() async { + await d.dir('static', [d.file('logo', 'file-contents')]).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + handler = const Pipeline().addHandler( + StaticHandler.directory( + staticRoot, + cacheControl: (_, _) => null, + cacheBustingConfig: CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@', + ), + ).asHandler, + ); + }); - test('when requesting asset with a non-busted URL then it serves the asset', + test( + 'when requesting asset with a non-busted URL then it serves the asset', () async { - final response = - await makeRequest(handler, '/static/logo', handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'file-contents'); - }); + final response = await makeRequest( + handler, + '/static/logo', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'file-contents'); + }, + ); - test( + test( 'when requesting asset with a cache busted URL then it serves the asset', () async { - final response = - await makeRequest(handler, '/static/logo@abc', handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'file-contents'); - }); - }); + final response = await makeRequest( + handler, + '/static/logo@abc', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'file-contents'); + }, + ); + }, + ); group( - 'Given a static asset served through cache busting static file handler with a custom separator', - () { - late Handler handler; - setUp(() async { - await d.dir('static', [d.file('logo.png', 'png-bytes')]).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - handler = const Pipeline().addHandler(StaticHandler.directory( - staticRoot, - cacheControl: (final _, final __) => null, - cacheBustingConfig: CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '--', - ), - ).asHandler); - }); + 'Given a static asset served through cache busting static file handler with a custom separator', + () { + late Handler handler; + setUp(() async { + await d.dir('static', [d.file('logo.png', 'png-bytes')]).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + handler = const Pipeline().addHandler( + StaticHandler.directory( + staticRoot, + cacheControl: (_, _) => null, + cacheBustingConfig: CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '--', + ), + ).asHandler, + ); + }); - test('when requesting asset with a non-busted URL then it serves the asset', + test( + 'when requesting asset with a non-busted URL then it serves the asset', () async { - final response = - await makeRequest(handler, '/static/logo.png', handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); + final response = await makeRequest( + handler, + '/static/logo.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); - test( + test( 'when requesting asset with a cache busted URL using the custom separator then it serves the asset', () async { - final response = await makeRequest(handler, '/static/logo--abc.png', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'png-bytes'); - }); + final response = await makeRequest( + handler, + '/static/logo--abc.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'png-bytes'); + }, + ); - test( + test( 'when requesting asset with a cache busted URL using the default separator then it does not find the asset', () async { - final response = await makeRequest(handler, '/static/logo@abc.png', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.notFound); - }); - }); + final response = await makeRequest( + handler, + '/static/logo@abc.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.notFound); + }, + ); + }, + ); group( - 'Given a static asset served through cache busting static handler in a nested directory containing the separator', - () { - late Handler handler; - setUp(() async { - await d.dir('static', [ - d.dir('@images', [d.file('logo.png', 'nested-bytes')]) - ]).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - handler = const Pipeline().addHandler(StaticHandler.directory( - staticRoot, - cacheControl: (final _, final __) => null, - cacheBustingConfig: CacheBustingConfig( - mountPrefix: '/static', - fileSystemRoot: staticRoot, - separator: '@', - ), - ).asHandler); - }); + 'Given a static asset served through cache busting static handler in a nested directory containing the separator', + () { + late Handler handler; + setUp(() async { + await d.dir('static', [ + d.dir('@images', [d.file('logo.png', 'nested-bytes')]), + ]).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + handler = const Pipeline().addHandler( + StaticHandler.directory( + staticRoot, + cacheControl: (_, _) => null, + cacheBustingConfig: CacheBustingConfig( + mountPrefix: '/static', + fileSystemRoot: staticRoot, + separator: '@', + ), + ).asHandler, + ); + }); - test('when requesting asset with a non-busted URL then it serves the asset', + test( + 'when requesting asset with a non-busted URL then it serves the asset', () async { - final response = await makeRequest(handler, '/static/@images/logo.png', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'nested-bytes'); - }); + final response = await makeRequest( + handler, + '/static/@images/logo.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'nested-bytes'); + }, + ); - test( + test( 'when requesting asset with a cache busted URL then it serves the asset', () async { - final response = await makeRequest( - handler, '/static/@images/logo@abc.png', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'nested-bytes'); - }); - }); + final response = await makeRequest( + handler, + '/static/@images/logo@abc.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'nested-bytes'); + }, + ); + }, + ); group( - 'Given a static handler configured with CacheBustingConfig that has different fileSystemRoot', - () { - late Handler handler; - setUp(() async { - await d.dir('static', [ - d.dir('images', [d.file('logo.png', 'nested-bytes')]) - ]).create(); - await d.dir('cache', []).create(); - final staticRoot = Directory(p.join(d.sandbox, 'static')); - final cacheRoot = Directory(p.join(d.sandbox, 'cache')); - handler = const Pipeline().addHandler(StaticHandler.directory( - staticRoot, - cacheControl: (final _, final __) => null, - cacheBustingConfig: CacheBustingConfig( - mountPrefix: '/cache', - fileSystemRoot: cacheRoot, - separator: '@', - ), - ).asHandler); - }); + 'Given a static handler configured with CacheBustingConfig that has different fileSystemRoot', + () { + late Handler handler; + setUp(() async { + await d.dir('static', [ + d.dir('images', [d.file('logo.png', 'nested-bytes')]), + ]).create(); + await d.dir('cache', []).create(); + final staticRoot = Directory(p.join(d.sandbox, 'static')); + final cacheRoot = Directory(p.join(d.sandbox, 'cache')); + handler = const Pipeline().addHandler( + StaticHandler.directory( + staticRoot, + cacheControl: (_, _) => null, + cacheBustingConfig: CacheBustingConfig( + mountPrefix: '/cache', + fileSystemRoot: cacheRoot, + separator: '@', + ), + ).asHandler, + ); + }); - test('when requesting asset with a non-busted URL then it serves the asset', + test( + 'when requesting asset with a non-busted URL then it serves the asset', () async { - final response = await makeRequest(handler, '/static/images/logo.png', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'nested-bytes'); - }); + final response = await makeRequest( + handler, + '/static/images/logo.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'nested-bytes'); + }, + ); - test( + test( 'when requesting asset with a cache busted URL then it still serves the asset (handler-level cache busting)', () async { - final response = await makeRequest(handler, '/static/images/logo@abc.png', - handlerPath: 'static'); - expect(response.statusCode, HttpStatus.ok); - expect(await response.readAsString(), 'nested-bytes'); - }); - }); + final response = await makeRequest( + handler, + '/static/images/logo@abc.png', + handlerPath: 'static', + ); + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), 'nested-bytes'); + }, + ); + }, + ); } diff --git a/test/static/cache_control_test.dart b/test/static/cache_control_test.dart index 7b5aacde..a7998391 100644 --- a/test/static/cache_control_test.dart +++ b/test/static/cache_control_test.dart @@ -22,27 +22,33 @@ void main() { }); test( - 'Given Cache-Control header is set on the server' - 'when a file is served, ' - 'then the response includes the specified Cache-Control header', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => cacheControl).asHandler; - final response = await makeRequest(handler, '/test_file.txt'); + 'Given Cache-Control header is set on the server' + 'when a file is served, ' + 'then the response includes the specified Cache-Control header', + () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => cacheControl, + ).asHandler; + final response = await makeRequest(handler, '/test_file.txt'); - expect(response.statusCode, HttpStatus.ok); - expect(response.headers.cacheControl, isNotNull); - expect(response.headers.cacheControl!.maxAge, 3600); - expect(response.headers.cacheControl!.publicCache, isTrue); - expect(response.headers.cacheControl!.mustRevalidate, isTrue); - }); + expect(response.statusCode, HttpStatus.ok); + expect(response.headers.cacheControl, isNotNull); + expect(response.headers.cacheControl!.maxAge, 3600); + expect(response.headers.cacheControl!.publicCache, isTrue); + expect(response.headers.cacheControl!.mustRevalidate, isTrue); + }, + ); - test( - 'Given no Cache-Control header is specified on the server, ' + test('Given no Cache-Control header is specified on the server, ' 'when a file is served, ' 'then the response includes no Cache-Control header', () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest(handler, '/test_file.txt'); expect(response.statusCode, HttpStatus.ok); @@ -52,32 +58,36 @@ void main() { group('Given Cache-Control header is set for a file pattern', () { late Handler handler; setUpAll(() { - handler = StaticHandler.directory( - Directory(d.sandbox), - cacheControl: (final _, final fileInfo) => - fileInfo.file.path.endsWith('jpg') ? cacheControl : null, - ).asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: + (_, final fileInfo) => + fileInfo.file.path.endsWith('jpg') ? cacheControl : null, + ).asHandler; }); test( - 'when a matching file is served, ' - 'then the response includes the specified Cache-Control header', - () async { - final response = await makeRequest(handler, '/image.jpg'); - expect(response.statusCode, HttpStatus.ok); - expect(response.headers.cacheControl, isNotNull); - expect(response.headers.cacheControl!.maxAge, 3600); - expect(response.headers.cacheControl!.publicCache, isTrue); - expect(response.headers.cacheControl!.mustRevalidate, isTrue); - }); + 'when a matching file is served, ' + 'then the response includes the specified Cache-Control header', + () async { + final response = await makeRequest(handler, '/image.jpg'); + expect(response.statusCode, HttpStatus.ok); + expect(response.headers.cacheControl, isNotNull); + expect(response.headers.cacheControl!.maxAge, 3600); + expect(response.headers.cacheControl!.publicCache, isTrue); + expect(response.headers.cacheControl!.mustRevalidate, isTrue); + }, + ); test( - 'when a non-matching file is served, ' - "then the response doesn't includes the specified Cache-Control header", - () async { - final response = await makeRequest(handler, '/test_file.txt'); - expect(response.statusCode, HttpStatus.ok); - expect(response.headers.cacheControl, isNull); - }); + 'when a non-matching file is served, ' + "then the response doesn't includes the specified Cache-Control header", + () async { + final response = await makeRequest(handler, '/test_file.txt'); + expect(response.statusCode, HttpStatus.ok); + expect(response.headers.cacheControl, isNull); + }, + ); }); } diff --git a/test/static/content_type_test.dart b/test/static/content_type_test.dart index f06b7e91..4ddb7386 100644 --- a/test/static/content_type_test.dart +++ b/test/static/content_type_test.dart @@ -26,29 +26,34 @@ void main() { await d.file('image.svg', '').create(); await d.file('app.wasm', 'fake wasm content').create(); await d.file('no_extension', 'content without extension').create(); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; }); parameterizedTest( - (final v) => 'Given a file "${v.key}", ' + (final v) => + 'Given a file "${v.key}", ' 'when requested, ' 'then the mimetype is "${v.value.toHeaderValue()}"', - variants: { - '/test.html': MimeType.html, - '/test.json': MimeType.json, - '/test.css': MimeType.css, - '/test.js': MimeType.javascript, - '/test.txt': MimeType.plainText, - '/test.xml': MimeType.xml, - '/test.pdf': MimeType.pdf, - '/image.jpg': const MimeType('image', 'jpeg'), - '/image.png': const MimeType('image', 'png'), - '/image.gif': const MimeType('image', 'gif'), - '/image.svg': const MimeType('image', 'svg+xml'), - '/app.wasm': const MimeType('application', 'wasm'), - '/no_extension': MimeType.octetStream, - }.entries, + variants: + { + '/test.html': MimeType.html, + '/test.json': MimeType.json, + '/test.css': MimeType.css, + '/test.js': MimeType.javascript, + '/test.txt': MimeType.plainText, + '/test.xml': MimeType.xml, + '/test.pdf': MimeType.pdf, + '/image.jpg': const MimeType('image', 'jpeg'), + '/image.png': const MimeType('image', 'png'), + '/image.gif': const MimeType('image', 'gif'), + '/image.svg': const MimeType('image', 'svg+xml'), + '/app.wasm': const MimeType('application', 'wasm'), + '/no_extension': MimeType.octetStream, + }.entries, (final v) async { final fileName = v.key; final mimeType = v.value; @@ -62,34 +67,39 @@ void main() { group('Given a custom MIME type resolver', () { test( - 'Given a custom MIME resolver that maps .txt to application/x-my-text, ' - 'when a .txt file is requested, ' - 'then the Content-Type is application/x-my-text', () async { - final customResolver = MimeTypeResolver() - ..addExtension('txt', 'application/x-my-text'); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null, - mimeResolver: customResolver) - .asHandler; - - final response = await makeRequest(handler, '/test.txt'); - - expect(response.statusCode, HttpStatus.ok); - expect(response.body.bodyType!.mimeType.primaryType, 'application'); - expect(response.body.bodyType!.mimeType.subType, 'x-my-text'); - }); - - test( - 'Given a custom MIME resolver that maps .custom to text/custom, ' + 'Given a custom MIME resolver that maps .txt to application/x-my-text, ' + 'when a .txt file is requested, ' + 'then the Content-Type is application/x-my-text', + () async { + final customResolver = + MimeTypeResolver()..addExtension('txt', 'application/x-my-text'); + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + mimeResolver: customResolver, + ).asHandler; + + final response = await makeRequest(handler, '/test.txt'); + + expect(response.statusCode, HttpStatus.ok); + expect(response.body.bodyType!.mimeType.primaryType, 'application'); + expect(response.body.bodyType!.mimeType.subType, 'x-my-text'); + }, + ); + + test('Given a custom MIME resolver that maps .custom to text/custom, ' 'when a .custom file is requested, ' 'then the Content-Type is text/custom', () async { await d.file('test.custom', 'custom file content').create(); - final customResolver = MimeTypeResolver() - ..addExtension('custom', 'text/custom'); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null, - mimeResolver: customResolver) - .asHandler; + final customResolver = + MimeTypeResolver()..addExtension('custom', 'text/custom'); + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + mimeResolver: customResolver, + ).asHandler; final response = await makeRequest(handler, '/test.custom'); @@ -99,8 +109,7 @@ void main() { }); }); - test( - 'Given a text file, ' + test('Given a text file, ' 'when requested, ' 'then the Content-Type includes charset=utf-8', () async { final response = await makeRequest(handler, '/test.html'); @@ -112,8 +121,7 @@ void main() { expect(response.body.bodyType?.encoding?.name, 'utf-8'); }); - test( - 'Given a non-text file, ' + test('Given a non-text file, ' 'when requested, ' 'then the Content-Type does not include charset', () async { final response = await makeRequest(handler, '/image.jpg'); diff --git a/test/static/create_file_handler_test.dart b/test/static/create_file_handler_test.dart index cce6617c..50d4e602 100644 --- a/test/static/create_file_handler_test.dart +++ b/test/static/create_file_handler_test.dart @@ -14,10 +14,11 @@ void main() { }); test('Given a file when served then it returns the file contents', () async { - final handler = StaticHandler.file( - File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler; + final handler = + StaticHandler.file( + File(p.join(d.sandbox, 'file.txt')), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest(handler, '/file.txt'); expect(response.statusCode, HttpStatus.ok); expect(response.body.contentLength, 8); @@ -25,13 +26,14 @@ void main() { }); test('Given a non-matching URL when served then it returns a 404', () async { - final router = RelicRouter() - ..get( + final router = + RelicRouter()..get( '/foo/bar', StaticHandler.file( File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler); + cacheControl: (_, _) => null, + ).asHandler, + ); final handler = router.asHandler; final response = await makeRequest(handler, '/foo/file.txt'); @@ -39,45 +41,50 @@ void main() { }); test( - 'Given a file under a custom URL when served then it returns the file contents', - () async { - final router = RelicRouter() - ..get( - '/foo/bar', - StaticHandler.file( - File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler); - - final handler = router.asHandler; - final response = await makeRequest(handler, '/foo/bar'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 8); - expect(response.readAsString(), completion('contents')); - }); + 'Given a file under a custom URL when served then it returns the file contents', + () async { + final router = + RelicRouter()..get( + '/foo/bar', + StaticHandler.file( + File(p.join(d.sandbox, 'file.txt')), + cacheControl: (_, _) => null, + ).asHandler, + ); + + final handler = router.asHandler; + final response = await makeRequest(handler, '/foo/bar'); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 8); + expect(response.readAsString(), completion('contents')); + }, + ); test( - "Given a custom URL that isn't matched when served then it returns a 404", - () async { - final router = RelicRouter() - ..get( - '/foo/bar', - StaticHandler.file( - File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler); - - final handler = router.asHandler; - final response = await makeRequest(handler, '/file.txt'); - expect(response.statusCode, HttpStatus.notFound); - }); + "Given a custom URL that isn't matched when served then it returns a 404", + () async { + final router = + RelicRouter()..get( + '/foo/bar', + StaticHandler.file( + File(p.join(d.sandbox, 'file.txt')), + cacheControl: (_, _) => null, + ).asHandler, + ); + + final handler = router.asHandler; + final response = await makeRequest(handler, '/file.txt'); + expect(response.statusCode, HttpStatus.notFound); + }, + ); group('Given the content type header', () { test('when inferred from the file path then it is set correctly', () async { - final handler = StaticHandler.file( - File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler; + final handler = + StaticHandler.file( + File(p.join(d.sandbox, 'file.txt')), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest(handler, '/file.txt'); expect(response.statusCode, HttpStatus.ok); expect(response.mimeType?.primaryType, 'text'); @@ -85,70 +92,77 @@ void main() { }); test( - "when it can't be inferred then it defaults to application/octet-stream", - () async { - final handler = StaticHandler.file( - File(p.join(d.sandbox, 'random.unknown')), - cacheControl: (final _, final __) => null, - ).asHandler; - final response = await makeRequest(handler, '/random.unknown'); - expect(response.statusCode, HttpStatus.ok); - expect(response.mimeType, MimeType.octetStream); - }); + "when it can't be inferred then it defaults to application/octet-stream", + () async { + final handler = + StaticHandler.file( + File(p.join(d.sandbox, 'random.unknown')), + cacheControl: (_, _) => null, + ).asHandler; + final response = await makeRequest(handler, '/random.unknown'); + expect(response.statusCode, HttpStatus.ok); + expect(response.mimeType, MimeType.octetStream); + }, + ); }); group('Given the content range header', () { - test('when bytes from 0 to 4 are requested then it returns partial content', - () async { - final handler = StaticHandler.file( - File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler; - final response = await makeRequest( - handler, - '/file.txt', - headers: Headers.build( - (final mh) => mh.range = RangeHeader.parse('bytes=0-4'), - ), - ); - expect(response.statusCode, HttpStatus.partialContent); - expect(response.headers.acceptRanges?.isBytes, isTrue); - expect(response.headers.contentRange?.start, 0); - expect(response.headers.contentRange?.end, 4); - expect(response.headers.contentRange?.size, 8); - }); - test( - 'when range at the end overflows from 0 to 9 then it returns partial content', - () async { - final handler = StaticHandler.file( - File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler; - final response = await makeRequest( - handler, - '/file.txt', - headers: Headers.build( - (final mh) => mh.range = RangeHeader.parse('bytes=0-9'), - ), - ); - expect(response.statusCode, HttpStatus.partialContent); - expect(response.headers.acceptRanges?.isBytes, isTrue); - - expect(response.headers.contentRange?.start, 0); - expect(response.headers.contentRange?.end, 7); - expect(response.headers.contentRange?.size, 8); - - expect(response.body.contentLength, 8); - }); + 'when bytes from 0 to 4 are requested then it returns partial content', + () async { + final handler = + StaticHandler.file( + File(p.join(d.sandbox, 'file.txt')), + cacheControl: (_, _) => null, + ).asHandler; + final response = await makeRequest( + handler, + '/file.txt', + headers: Headers.build( + (final mh) => mh.range = RangeHeader.parse('bytes=0-4'), + ), + ); + expect(response.statusCode, HttpStatus.partialContent); + expect(response.headers.acceptRanges?.isBytes, isTrue); + expect(response.headers.contentRange?.start, 0); + expect(response.headers.contentRange?.end, 4); + expect(response.headers.contentRange?.size, 8); + }, + ); test( - 'when range at the start overflows from 8 to 9, ' + 'when range at the end overflows from 0 to 9 then it returns partial content', + () async { + final handler = + StaticHandler.file( + File(p.join(d.sandbox, 'file.txt')), + cacheControl: (_, _) => null, + ).asHandler; + final response = await makeRequest( + handler, + '/file.txt', + headers: Headers.build( + (final mh) => mh.range = RangeHeader.parse('bytes=0-9'), + ), + ); + expect(response.statusCode, HttpStatus.partialContent); + expect(response.headers.acceptRanges?.isBytes, isTrue); + + expect(response.headers.contentRange?.start, 0); + expect(response.headers.contentRange?.end, 7); + expect(response.headers.contentRange?.size, 8); + + expect(response.body.contentLength, 8); + }, + ); + + test('when range at the start overflows from 8 to 9, ' 'then it returns 416 Request Range Not Satisfiable', () async { - final handler = StaticHandler.file( - File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler; + final handler = + StaticHandler.file( + File(p.join(d.sandbox, 'file.txt')), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest( handler, '/file.txt', @@ -162,13 +176,13 @@ void main() { expect(response.headers.acceptRanges?.isBytes, isTrue); }); - test( - 'when invalid request with start > end is received, ' + test('when invalid request with start > end is received, ' 'then it returns 416 Request Range Not Satisfiable', () async { - final handler = StaticHandler.file( - File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler; + final handler = + StaticHandler.file( + File(p.join(d.sandbox, 'file.txt')), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest( handler, '/file.txt', @@ -181,13 +195,13 @@ void main() { expect(response.headers.acceptRanges?.isBytes, isTrue); }); - test( - 'when request with start > end is received, ' + test('when request with start > end is received, ' 'then it returns 416 Request Range Not Satisfiable', () async { - final handler = StaticHandler.file( - File(p.join(d.sandbox, 'file.txt')), - cacheControl: (final _, final __) => null, - ).asHandler; + final handler = + StaticHandler.file( + File(p.join(d.sandbox, 'file.txt')), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest( handler, '/file.txt', @@ -206,7 +220,7 @@ void main() { expect( () => StaticHandler.file( File(p.join(d.sandbox, 'nothing.txt')), - cacheControl: (final _, final __) => null, + cacheControl: (_, _) => null, ), throwsArgumentError, ); diff --git a/test/static/default_handler_test.dart b/test/static/default_handler_test.dart index 863e10a4..355068bb 100644 --- a/test/static/default_handler_test.dart +++ b/test/static/default_handler_test.dart @@ -16,42 +16,37 @@ void main() { ]).create(); // Return 403 Forbidden instead of 404 Not Found, as default - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null, - defaultHandler: respondWith((final _) => Response.forbidden())) - .asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + defaultHandler: respondWith((_) => Response.forbidden()), + ).asHandler; }); - test( - 'when accessing exisiting directory "/files" ' - 'then it returns 403 Forbidden', - () async { - final response = await makeRequest(handler, '/files'); - expect(response.statusCode, HttpStatus.forbidden); - }, - ); - - test( - 'when accessing existing file "/files/index.html" ' - 'then it returns the default document', - () async { - final response = await makeRequest(handler, '/files/index.html'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 31); - expect(response.readAsString(), - completion('files')); - expect(response.mimeType?.primaryType, 'text'); - expect(response.mimeType?.subType, 'html'); - }, - ); - - test( - 'when accessing non-existing file entity "/files/none" ' - 'then it returns 403 Forbidden', - () async { - final response = await makeRequest(handler, '/files/none'); - expect(response.statusCode, HttpStatus.forbidden); - }, - ); + test('when accessing exisiting directory "/files" ' + 'then it returns 403 Forbidden', () async { + final response = await makeRequest(handler, '/files'); + expect(response.statusCode, HttpStatus.forbidden); + }); + + test('when accessing existing file "/files/index.html" ' + 'then it returns the default document', () async { + final response = await makeRequest(handler, '/files/index.html'); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 31); + expect( + response.readAsString(), + completion('files'), + ); + expect(response.mimeType?.primaryType, 'text'); + expect(response.mimeType?.subType, 'html'); + }); + + test('when accessing non-existing file entity "/files/none" ' + 'then it returns 403 Forbidden', () async { + final response = await makeRequest(handler, '/files/none'); + expect(response.statusCode, HttpStatus.forbidden); + }); }); } diff --git a/test/static/get_handler_test.dart b/test/static/get_handler_test.dart index 089af0b3..fb28fb25 100644 --- a/test/static/get_handler_test.dart +++ b/test/static/get_handler_test.dart @@ -10,45 +10,65 @@ void main() { await d.file('root.txt', 'root txt').create(); await d.dir('files', [ d.file('test.txt', 'test txt content'), - d.file('with space.txt', 'with space content') + d.file('with space.txt', 'with space content'), ]).create(); }); test( - 'Given a non-existent relative path when creating a static handler then it throws an ArgumentError', - () async { - expect( - () => StaticHandler.directory(Directory('random/relative'), - cacheControl: (final _, final __) => null).asHandler, - throwsArgumentError); - }); + 'Given a non-existent relative path when creating a static handler then it throws an ArgumentError', + () async { + expect( + () => + StaticHandler.directory( + Directory('random/relative'), + cacheControl: (_, _) => null, + ).asHandler, + throwsArgumentError, + ); + }, + ); test( - 'Given an existing relative path when creating a static handler then it returns normally', - () async { - final existingRelative = p.relative(d.sandbox); - expect( - () => StaticHandler.directory(Directory(existingRelative), - cacheControl: (final _, final __) => null).asHandler, - returnsNormally); - }); + 'Given an existing relative path when creating a static handler then it returns normally', + () async { + final existingRelative = p.relative(d.sandbox); + expect( + () => + StaticHandler.directory( + Directory(existingRelative), + cacheControl: (_, _) => null, + ).asHandler, + returnsNormally, + ); + }, + ); test( - 'Given a non-existent absolute path when creating a static handler then it throws an ArgumentError', - () { - final nonExistingAbsolute = p.join(d.sandbox, 'not_here'); - expect( - () => StaticHandler.directory(Directory(nonExistingAbsolute), - cacheControl: (final _, final __) => null).asHandler, - throwsArgumentError); - }); + 'Given a non-existent absolute path when creating a static handler then it throws an ArgumentError', + () { + final nonExistingAbsolute = p.join(d.sandbox, 'not_here'); + expect( + () => + StaticHandler.directory( + Directory(nonExistingAbsolute), + cacheControl: (_, _) => null, + ).asHandler, + throwsArgumentError, + ); + }, + ); test( - 'Given an existing absolute path when creating a static handler then it returns normally', - () { - expect( - () => StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler, - returnsNormally); - }); + 'Given an existing absolute path when creating a static handler then it returns normally', + () { + expect( + () => + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler, + returnsNormally, + ); + }, + ); } diff --git a/test/static/if_modified_since_test.dart b/test/static/if_modified_since_test.dart index 71683cc4..8ca01d19 100644 --- a/test/static/if_modified_since_test.dart +++ b/test/static/if_modified_since_test.dart @@ -12,30 +12,35 @@ void main() { setUp(() async { await d.file('test_file.txt', fileContent).create(); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; }); - test( - 'Given an If-Modified-Since header with a matching date, ' + test('Given an If-Modified-Since header with a matching date, ' 'when a request is made for the file, ' 'then a 304 Not Modified status is returned with no body', () async { final initialResponse = await makeRequest(handler, '/test_file.txt'); final lastModified = initialResponse.headers.lastModified!; - final headers = - Headers.build((final mh) => mh.ifModifiedSince = lastModified); + final headers = Headers.build( + (final mh) => mh.ifModifiedSince = lastModified, + ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.notModified); expect(response.body.contentLength, 0); expect(await response.readAsString(), isEmpty); }); - test( - 'Given an If-Modified-Since header with an earlier date, ' + test('Given an If-Modified-Since header with an earlier date, ' 'when a request is made for the file, ' 'then a 200 OK status is returned with the full body', () async { final initialResponse = await makeRequest(handler, '/test_file.txt'); @@ -44,19 +49,22 @@ void main() { // Create an earlier date, simulating the client's cached date final earlierDate = lastModified.subtract(const Duration(days: 1)); - final headers = - Headers.build((final mh) => mh.ifModifiedSince = earlierDate); + final headers = Headers.build( + (final mh) => mh.ifModifiedSince = earlierDate, + ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.ok); expect(response.body.contentLength, fileContent.length); expect(response.readAsString(), completion(fileContent)); }); - test( - 'Given an If-Modified-Since header with a future date, ' + test('Given an If-Modified-Since header with a future date, ' 'when a request is made for the file, ' 'then a 304 Not Modified status is returned with no body', () async { final initialResponse = await makeRequest(handler, '/test_file.txt'); @@ -65,11 +73,15 @@ void main() { // Create a future date, simulating a client with a somehow "newer" cached date final futureDate = lastModified.add(const Duration(days: 1)); - final headers = - Headers.build((final mh) => mh.ifModifiedSince = futureDate); + final headers = Headers.build( + (final mh) => mh.ifModifiedSince = futureDate, + ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.notModified); expect(response.body.contentLength, 0); diff --git a/test/static/if_none_match_test.dart b/test/static/if_none_match_test.dart index ed12d304..046106da 100644 --- a/test/static/if_none_match_test.dart +++ b/test/static/if_none_match_test.dart @@ -12,41 +12,47 @@ void main() { setUp(() async { await d.file('test_file.txt', fileContent).create(); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; }); - test( - 'Given an If-None-Match header with a matching ETag, ' + test('Given an If-None-Match header with a matching ETag, ' 'when a request is made for the file, ' 'then a 304 Not Modified status is returned with no body', () async { final initialResponse = await makeRequest(handler, '/test_file.txt'); final etag = initialResponse.headers.etag!.value; - final headers = - Headers.build((final mh) => mh.ifNoneMatch = IfNoneMatchHeader.etags( - [ETagHeader(value: etag)], - )); + final headers = Headers.build( + (final mh) => + mh.ifNoneMatch = IfNoneMatchHeader.etags([ETagHeader(value: etag)]), + ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.notModified); expect(response.body.contentLength, 0); expect(await response.readAsString(), isEmpty); }); - test( - 'Given an If-None-Match header with a non-matching ETag, ' + test('Given an If-None-Match header with a non-matching ETag, ' 'when a request is made for the file, ' 'then a 200 OK status is returned with the full body', () async { const nonMatchingEtag = ETagHeader(value: 'non-existent-etag'); - final headers = - Headers.build((final mh) => mh.ifNoneMatch = IfNoneMatchHeader.etags( - [nonMatchingEtag], - )); + final headers = Headers.build( + (final mh) => mh.ifNoneMatch = IfNoneMatchHeader.etags([nonMatchingEtag]), + ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.ok); expect(response.body.contentLength, fileContent.length); @@ -54,63 +60,82 @@ void main() { }); test( - 'Given an If-None-Match header with multiple ETags including a matching one, ' - 'when a request is made for the file, ' - 'then a 304 Not Modified status is returned with no body', () async { - final initialResponse = await makeRequest(handler, '/test_file.txt'); - final etag = initialResponse.headers.etag!.value; - final headers = - Headers.build((final mh) => mh.ifNoneMatch = IfNoneMatchHeader.etags([ + 'Given an If-None-Match header with multiple ETags including a matching one, ' + 'when a request is made for the file, ' + 'then a 304 Not Modified status is returned with no body', + () async { + final initialResponse = await makeRequest(handler, '/test_file.txt'); + final etag = initialResponse.headers.etag!.value; + final headers = Headers.build( + (final mh) => + mh.ifNoneMatch = IfNoneMatchHeader.etags([ const ETagHeader(value: 'first-non-matching'), ETagHeader(value: etag), // correct value const ETagHeader(value: 'second-non-matching'), - ])); + ]), + ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); - expect(response.statusCode, HttpStatus.notModified); - expect(response.body.contentLength, 0); - expect(await response.readAsString(), isEmpty); - }); + expect(response.statusCode, HttpStatus.notModified); + expect(response.body.contentLength, 0); + expect(await response.readAsString(), isEmpty); + }, + ); test( - 'Given an If-None-Match header with an original ETag that no longer match, ' - 'when a request is made for the file, ' - 'then a 200 OK status is returned with the full body', () async { - await d.file('changing', 'before').create(); + 'Given an If-None-Match header with an original ETag that no longer match, ' + 'when a request is made for the file, ' + 'then a 200 OK status is returned with the full body', + () async { + await d.file('changing', 'before').create(); - final firstResponse = await makeRequest(handler, '/changing'); - expect(firstResponse.statusCode, 200); - expect(await firstResponse.readAsString(), 'before'); + final firstResponse = await makeRequest(handler, '/changing'); + expect(firstResponse.statusCode, 200); + expect(await firstResponse.readAsString(), 'before'); - final etag = firstResponse.headers.etag!; + final etag = firstResponse.headers.etag!; - final secondResponse = await makeRequest(handler, '/changing', + final secondResponse = await makeRequest( + handler, + '/changing', headers: Headers.build( - (final mh) => mh.ifNoneMatch = IfNoneMatchHeader.etags([etag]))); + (final mh) => mh.ifNoneMatch = IfNoneMatchHeader.etags([etag]), + ), + ); - expect(secondResponse.statusCode, 304); - expect(await secondResponse.readAsString(), isEmpty); + expect(secondResponse.statusCode, 304); + expect(await secondResponse.readAsString(), isEmpty); - await d.file('changing', 'after').create(); + await d.file('changing', 'after').create(); - final thirdResponse = await makeRequest(handler, '/changing', + final thirdResponse = await makeRequest( + handler, + '/changing', headers: Headers.build( - (final mh) => mh.ifNoneMatch = IfNoneMatchHeader.etags([etag]))); - expect(thirdResponse.statusCode, 200); - expect(await thirdResponse.readAsString(), 'after'); - }); - - test( - 'Given an If-None-Match header with wildcard, ' + (final mh) => mh.ifNoneMatch = IfNoneMatchHeader.etags([etag]), + ), + ); + expect(thirdResponse.statusCode, 200); + expect(await thirdResponse.readAsString(), 'after'); + }, + ); + + test('Given an If-None-Match header with wildcard, ' 'when a request is made for the file, ' 'then a 304 Not Modified status is returned with no body', () async { final headers = Headers.build( (final mh) => mh.ifNoneMatch = const IfNoneMatchHeader.wildcard(), ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.notModified); expect(response.body.contentLength, 0); diff --git a/test/static/if_range_test.dart b/test/static/if_range_test.dart index 4d0498f2..6021863d 100644 --- a/test/static/if_range_test.dart +++ b/test/static/if_range_test.dart @@ -13,40 +13,55 @@ void main() { setUp(() async { await d.file('test_file.txt', fileContent).create(); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; }); test( - 'Given an If-Range header with a matching ETag, ' - 'when a range request is made, ' - 'then a 206 Partial Content response with partial content is returned', - () async { - final initialResponse = await makeRequest(handler, '/test_file.txt'); - final etag = initialResponse.headers.etag!.value; - final headers = Headers.build((final mh) => mh - ..range = RangeHeader(ranges: [Range(start: 0, end: 4)]) - ..ifRange = IfRangeHeader(etag: ETagHeader(value: etag))); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 5); - expect(response.readAsString(), completion(fileContent.substring(0, 5))); - }); - - test( - 'Given an If-Range header with a non-matching ETag, ' + 'Given an If-Range header with a matching ETag, ' + 'when a range request is made, ' + 'then a 206 Partial Content response with partial content is returned', + () async { + final initialResponse = await makeRequest(handler, '/test_file.txt'); + final etag = initialResponse.headers.etag!.value; + final headers = Headers.build( + (final mh) => + mh + ..range = RangeHeader(ranges: [Range(start: 0, end: 4)]) + ..ifRange = IfRangeHeader(etag: ETagHeader(value: etag)), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 5); + expect(response.readAsString(), completion(fileContent.substring(0, 5))); + }, + ); + + test('Given an If-Range header with a non-matching ETag, ' 'when a range request is made, ' 'then a 200 OK status with full content is returned', () async { const nonMatchingEtag = ETagHeader(value: 'non-existent-etag'); - final headers = Headers.build((final mh) => mh - ..range = RangeHeader(ranges: [Range(end: 4)]) - ..ifRange = IfRangeHeader(etag: nonMatchingEtag)); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final headers = Headers.build( + (final mh) => + mh + ..range = RangeHeader(ranges: [Range(end: 4)]) + ..ifRange = IfRangeHeader(etag: nonMatchingEtag), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.ok); expect(response.body.contentLength, fileContent.length); @@ -54,40 +69,55 @@ void main() { }); test( - 'Given an If-Range header with a matching Last-Modified date, ' - 'when a range request is made, ' - 'then a 206 Partial Content response with partial content is returned', - () async { - final rootPath = p.join(d.sandbox, 'test_file.txt'); - final modified = File(rootPath).statSync().modified.toUtc(); - final headers = Headers.build((final mh) => mh - ..range = RangeHeader(ranges: [Range(start: 0, end: 4)]) - ..ifRange = IfRangeHeader(lastModified: modified)); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 5); - expect(response.readAsString(), completion(fileContent.substring(0, 5))); - }); + 'Given an If-Range header with a matching Last-Modified date, ' + 'when a range request is made, ' + 'then a 206 Partial Content response with partial content is returned', + () async { + final rootPath = p.join(d.sandbox, 'test_file.txt'); + final modified = File(rootPath).statSync().modified.toUtc(); + final headers = Headers.build( + (final mh) => + mh + ..range = RangeHeader(ranges: [Range(start: 0, end: 4)]) + ..ifRange = IfRangeHeader(lastModified: modified), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 5); + expect(response.readAsString(), completion(fileContent.substring(0, 5))); + }, + ); test( - 'Given an If-Range header with a non-matching (earlier) Last-Modified date, ' - 'when a range request is made, ' - 'then a 200 OK status with full content is returned', () async { - final rootPath = p.join(d.sandbox, 'test_file.txt'); - final modified = File(rootPath).statSync().modified.toUtc(); - final earlierModified = modified.subtract(const Duration(days: 1)); - final headers = Headers.build((final mh) => mh - ..range = RangeHeader(ranges: [Range(start: 0, end: 4)]) - ..ifRange = IfRangeHeader(lastModified: earlierModified)); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, fileContent.length); - expect(response.readAsString(), completion(fileContent)); - }); + 'Given an If-Range header with a non-matching (earlier) Last-Modified date, ' + 'when a range request is made, ' + 'then a 200 OK status with full content is returned', + () async { + final rootPath = p.join(d.sandbox, 'test_file.txt'); + final modified = File(rootPath).statSync().modified.toUtc(); + final earlierModified = modified.subtract(const Duration(days: 1)); + final headers = Headers.build( + (final mh) => + mh + ..range = RangeHeader(ranges: [Range(start: 0, end: 4)]) + ..ifRange = IfRangeHeader(lastModified: earlierModified), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, fileContent.length); + expect(response.readAsString(), completion(fileContent)); + }, + ); } diff --git a/test/static/mounted_test.dart b/test/static/mounted_test.dart index d28fd20e..4e456f9d 100644 --- a/test/static/mounted_test.dart +++ b/test/static/mounted_test.dart @@ -11,17 +11,17 @@ void main() { await d.file('root.txt', 'root txt').create(); }); - test( - 'Given a static handler mounted on a router under "/**" ' + test('Given a static handler mounted on a router under "/**" ' 'when retrieving the same file twice ' 'then it should return 200 Ok both times', () async { - final router = RelicRouter() - ..get( + final router = + RelicRouter()..get( '/**', StaticHandler.directory( Directory(d.sandbox), - cacheControl: (final _, final __) => null, - ).asHandler); + cacheControl: (_, _) => null, + ).asHandler, + ); final handler = router.asHandler; diff --git a/test/static/not_found_test.dart b/test/static/not_found_test.dart index 92526221..1eee0b49 100644 --- a/test/static/not_found_test.dart +++ b/test/static/not_found_test.dart @@ -13,12 +13,14 @@ void main() { setUp(() async { await d.file('test_file.txt', fileContent).create(); await d.dir('test_directory').create(); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; }); - test( - 'Given a request for a non-existent file, ' + test('Given a request for a non-existent file, ' 'when the request is made, ' 'then a 404 Not Found status is returned', () async { final response = await makeRequest(handler, '/non_existent_file.txt'); @@ -28,12 +30,13 @@ void main() { expect(await response.readAsString(), contains('Not Found')); }); - test( - 'Given a request for a file in a non-existent directory, ' + test('Given a request for a file in a non-existent directory, ' 'when the request is made, ' 'then a 404 Not Found status is returned', () async { - final response = - await makeRequest(handler, '/non_existent_dir/some_file.txt'); + final response = await makeRequest( + handler, + '/non_existent_dir/some_file.txt', + ); expect(response.statusCode, HttpStatus.notFound); expect(response.body.contentLength, greaterThan(0)); // "Not Found" message @@ -41,31 +44,38 @@ void main() { }); test( - 'Given a request for an existing directory, ' - 'when the request is made, ' - 'then a 404 Not Found status is returned (no directory listings)', - () async { - final response = await makeRequest(handler, '/test_directory/'); - - expect(response.statusCode, HttpStatus.notFound); - expect(response.body.contentLength, greaterThan(0)); // "Not Found" message - expect(await response.readAsString(), contains('Not Found')); - }); + 'Given a request for an existing directory, ' + 'when the request is made, ' + 'then a 404 Not Found status is returned (no directory listings)', + () async { + final response = await makeRequest(handler, '/test_directory/'); + + expect(response.statusCode, HttpStatus.notFound); + expect( + response.body.contentLength, + greaterThan(0), + ); // "Not Found" message + expect(await response.readAsString(), contains('Not Found')); + }, + ); test( - 'Given a request for an existing directory without trailing slash, ' - 'when the request is made, ' - 'then a 404 Not Found status is returned (no directory listings)', - () async { - final response = await makeRequest(handler, '/test_directory'); - - expect(response.statusCode, HttpStatus.notFound); - expect(response.body.contentLength, greaterThan(0)); // "Not Found" message - expect(await response.readAsString(), contains('Not Found')); - }); - - test( - 'Given a request with path traversal attempt, ' + 'Given a request for an existing directory without trailing slash, ' + 'when the request is made, ' + 'then a 404 Not Found status is returned (no directory listings)', + () async { + final response = await makeRequest(handler, '/test_directory'); + + expect(response.statusCode, HttpStatus.notFound); + expect( + response.body.contentLength, + greaterThan(0), + ); // "Not Found" message + expect(await response.readAsString(), contains('Not Found')); + }, + ); + + test('Given a request with path traversal attempt, ' 'when the request is made, ' 'then a 404 Not Found status is returned', () async { final response = await makeRequest(handler, '/../../../etc/passwd'); @@ -75,12 +85,13 @@ void main() { expect(await response.readAsString(), contains('Not Found')); }); - test( - 'Given a request with URL encoded path traversal attempt, ' + test('Given a request with URL encoded path traversal attempt, ' 'when the request is made, ' 'then a 404 Not Found status is returned', () async { - final response = - await makeRequest(handler, '/%2E%2E%2F%2E%2E%2Fetc%2Fpasswd'); + final response = await makeRequest( + handler, + '/%2E%2E%2F%2E%2E%2Fetc%2Fpasswd', + ); expect(response.statusCode, HttpStatus.notFound); expect(response.body.contentLength, greaterThan(0)); // "Not Found" message diff --git a/test/static/range_edge_cases_test.dart b/test/static/range_edge_cases_test.dart index ca11e6bb..e4132ac5 100644 --- a/test/static/range_edge_cases_test.dart +++ b/test/static/range_edge_cases_test.dart @@ -15,94 +15,107 @@ void main() { setUp(() async { await d.file('test_file.txt', fileContent).create(); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; }); group('Given malformed Range headers', () { - test( - 'Given a syntactically invalid Range header, ' + test('Given a syntactically invalid Range header, ' 'when a request is made for the file, ' 'then an InvalidHeaderException is thrown', () async { final headers = Headers.fromMap({ - 'range': ['bytes=invalid'] + 'range': ['bytes=invalid'], }); expect( - () async => - await makeRequest(handler, '/test_file.txt', headers: headers), - throwsA(isA())); + () async => + await makeRequest(handler, '/test_file.txt', headers: headers), + throwsA(isA()), + ); }); - test( - 'Given a Range header with no "bytes=" prefix, ' + test('Given a Range header with no "bytes=" prefix, ' 'when a request is made for the file, ' 'then an InvalidHeaderException is thrown', () async { final headers = Headers.fromMap({ - 'range': ['0-5'] + 'range': ['0-5'], }); expect( - () async => - await makeRequest(handler, '/test_file.txt', headers: headers), - throwsA(isA())); + () async => + await makeRequest(handler, '/test_file.txt', headers: headers), + throwsA(isA()), + ); }); - test( - 'Given a completely malformed Range header, ' + test('Given a completely malformed Range header, ' 'when a request is made for the file, ' 'then an InvalidHeaderException is thrown', () async { final headers = Headers.fromMap({ - 'range': ['abc123xyz'] + 'range': ['abc123xyz'], }); expect( - () async => - await makeRequest(handler, '/test_file.txt', headers: headers), - throwsA(isA())); + () async => + await makeRequest(handler, '/test_file.txt', headers: headers), + throwsA(isA()), + ); }); }); group('Given potentially problematic Range requests', () { - test( - 'Given a Range request with start beyond file length, ' + test('Given a Range request with start beyond file length, ' 'when a request is made for the file, ' 'then a 416 Requested Range Not Satisfiable is returned', () async { // File is 16 bytes (0-15), requesting bytes starting from 100 - final headers = Headers.build((final mh) => - mh.range = RangeHeader(ranges: [Range(start: 100, end: 105)])); + final headers = Headers.build( + (final mh) => + mh.range = RangeHeader(ranges: [Range(start: 100, end: 105)]), + ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.requestedRangeNotSatisfiable); }); - test( - 'Given a suffix-byte-range-spec greater than file length, ' + test('Given a suffix-byte-range-spec greater than file length, ' 'when a request is made for the file, ' 'then content is returned', () async { // File is 16 bytes, requesting last 100 bytes final headers = Headers.build( - (final mh) => mh.range = RangeHeader(ranges: [Range(end: 100)])); + (final mh) => mh.range = RangeHeader(ranges: [Range(end: 100)]), + ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.partialContent); expect(response.body.contentLength, 16); }); - test( - 'Given a Range request where end < start, ' + test('Given a Range request where end < start, ' 'when a request is made for the file, ' 'then a 416 Requested Range Not Satisfiable is returned', () async { // Invalid range: end (2) < start (5) - final headers = Headers.build((final mh) => - mh.range = RangeHeader(ranges: [Range(start: 5, end: 2)])); + final headers = Headers.build( + (final mh) => mh.range = RangeHeader(ranges: [Range(start: 5, end: 2)]), + ); - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); expect(response.statusCode, HttpStatus.requestedRangeNotSatisfiable); }); @@ -110,150 +123,183 @@ void main() { group('Given boundary Range requests', () { test( - 'Given a Range request for the last byte, ' - 'when a request is made for the file, ' - 'then a 206 Partial Content status is returned with correct content', - () async { - // File is 16 bytes (0-15), requesting byte 15 - final headers = Headers.build((final mh) => - mh.range = RangeHeader(ranges: [Range(start: 15, end: 15)])); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 1); - expect(response.readAsString(), completion('F')); // Last character - - final contentRange = response.headers.contentRange; - expect(contentRange, isNotNull); - expect(contentRange!.start, 15); - expect(contentRange.end, 15); - expect(contentRange.size, fileContent.length); - }); + 'Given a Range request for the last byte, ' + 'when a request is made for the file, ' + 'then a 206 Partial Content status is returned with correct content', + () async { + // File is 16 bytes (0-15), requesting byte 15 + final headers = Headers.build( + (final mh) => + mh.range = RangeHeader(ranges: [Range(start: 15, end: 15)]), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 1); + expect(response.readAsString(), completion('F')); // Last character + + final contentRange = response.headers.contentRange; + expect(contentRange, isNotNull); + expect(contentRange!.start, 15); + expect(contentRange.end, 15); + expect(contentRange.size, fileContent.length); + }, + ); test( - 'Given a Range request for the first byte, ' - 'when a request is made for the file, ' - 'then a 206 Partial Content status is returned with correct content', - () async { - // File is 16 bytes (0-15), requesting byte 0 - final headers = Headers.build((final mh) => - mh.range = RangeHeader(ranges: [Range(start: 0, end: 0)])); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 1); - expect(response.readAsString(), completion('0')); // First character - - expect(response.headers.acceptRanges?.isBytes, isTrue); - final contentRange = response.headers.contentRange; - expect(contentRange, isNotNull); - expect(contentRange!.start, 0); - expect(contentRange.end, 0); - expect(contentRange.size, fileContent.length); - }); + 'Given a Range request for the first byte, ' + 'when a request is made for the file, ' + 'then a 206 Partial Content status is returned with correct content', + () async { + // File is 16 bytes (0-15), requesting byte 0 + final headers = Headers.build( + (final mh) => + mh.range = RangeHeader(ranges: [Range(start: 0, end: 0)]), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 1); + expect(response.readAsString(), completion('0')); // First character + + expect(response.headers.acceptRanges?.isBytes, isTrue); + final contentRange = response.headers.contentRange; + expect(contentRange, isNotNull); + expect(contentRange!.start, 0); + expect(contentRange.end, 0); + expect(contentRange.size, fileContent.length); + }, + ); test( - 'Given a Range request that extends to exactly the end of file, ' - 'when a request is made for the file, ' - 'then a 206 Partial Content status is returned with correct content', - () async { - // File is 16 bytes (0-15), requesting bytes 10-15 - final headers = Headers.build((final mh) => - mh.range = RangeHeader(ranges: [Range(start: 10, end: 15)])); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 6); - expect(response.readAsString(), completion('ABCDEF')); // Last 6 chars - - final contentRange = response.headers.contentRange; - expect(contentRange, isNotNull); - expect(contentRange!.start, 10); - expect(contentRange.end, 15); - expect(contentRange.size, fileContent.length); - }); + 'Given a Range request that extends to exactly the end of file, ' + 'when a request is made for the file, ' + 'then a 206 Partial Content status is returned with correct content', + () async { + // File is 16 bytes (0-15), requesting bytes 10-15 + final headers = Headers.build( + (final mh) => + mh.range = RangeHeader(ranges: [Range(start: 10, end: 15)]), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 6); + expect(response.readAsString(), completion('ABCDEF')); // Last 6 chars + + final contentRange = response.headers.contentRange; + expect(contentRange, isNotNull); + expect(contentRange!.start, 10); + expect(contentRange.end, 15); + expect(contentRange.size, fileContent.length); + }, + ); test( - 'Given a Range request that extends beyond the end of file, ' - 'when a request is made for the file, ' - 'then a 206 Partial Content response is returned with adjusted range', - () async { - // File is 16 bytes (0-15), requesting bytes 10-25 (beyond file end) - final headers = Headers.build((final mh) => - mh.range = RangeHeader(ranges: [Range(start: 10, end: 25)])); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 6); - expect(response.readAsString(), completion('ABCDEF')); // Last 6 chars - - final contentRange = response.headers.contentRange; - expect(contentRange, isNotNull); - expect(contentRange!.start, 10); - expect(contentRange.end, 15); - expect(contentRange.size, fileContent.length); - }); + 'Given a Range request that extends beyond the end of file, ' + 'when a request is made for the file, ' + 'then a 206 Partial Content response is returned with adjusted range', + () async { + // File is 16 bytes (0-15), requesting bytes 10-25 (beyond file end) + final headers = Headers.build( + (final mh) => + mh.range = RangeHeader(ranges: [Range(start: 10, end: 25)]), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 6); + expect(response.readAsString(), completion('ABCDEF')); // Last 6 chars + + final contentRange = response.headers.contentRange; + expect(contentRange, isNotNull); + expect(contentRange!.start, 10); + expect(contentRange.end, 15); + expect(contentRange.size, fileContent.length); + }, + ); }); group('Given suffix-byte-range-spec edge cases', () { test( - 'Given a suffix-byte-range-spec for the entire file, ' - 'when a request is made for the file, ' - 'then a 206 Partial Content status is returned with full content', - () async { - // File is 16 bytes, requesting last 16 bytes (entire file) - final headers = Headers.build( - (final mh) => mh.range = RangeHeader(ranges: [Range(end: 16)])); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, fileContent.length); - expect(response.readAsString(), completion(fileContent)); - - final contentRange = response.headers.contentRange; - expect(contentRange, isNotNull); - expect(contentRange!.start, 0); - expect(contentRange.end, 15); - expect(contentRange.size, fileContent.length); - }); + 'Given a suffix-byte-range-spec for the entire file, ' + 'when a request is made for the file, ' + 'then a 206 Partial Content status is returned with full content', + () async { + // File is 16 bytes, requesting last 16 bytes (entire file) + final headers = Headers.build( + (final mh) => mh.range = RangeHeader(ranges: [Range(end: 16)]), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, fileContent.length); + expect(response.readAsString(), completion(fileContent)); + + final contentRange = response.headers.contentRange; + expect(contentRange, isNotNull); + expect(contentRange!.start, 0); + expect(contentRange.end, 15); + expect(contentRange.size, fileContent.length); + }, + ); test( - 'Given a suffix-byte-range-spec for 1 byte, ' - 'when a request is made for the file, ' - 'then a 206 Partial Content status is returned with the last byte', - () async { - // File is 16 bytes, requesting last 1 byte - final headers = Headers.build( - (final mh) => mh.range = RangeHeader(ranges: [Range(end: 1)])); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 1); - expect(response.readAsString(), completion('F')); // Last character - - final contentRange = response.headers.contentRange; - expect(contentRange, isNotNull); - expect(contentRange!.start, 15); // Last byte is at index 15 - expect(contentRange.end, 15); - expect(contentRange.size, fileContent.length); - }); + 'Given a suffix-byte-range-spec for 1 byte, ' + 'when a request is made for the file, ' + 'then a 206 Partial Content status is returned with the last byte', + () async { + // File is 16 bytes, requesting last 1 byte + final headers = Headers.build( + (final mh) => mh.range = RangeHeader(ranges: [Range(end: 1)]), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 1); + expect(response.readAsString(), completion('F')); // Last character + + final contentRange = response.headers.contentRange; + expect(contentRange, isNotNull); + expect(contentRange!.start, 15); // Last byte is at index 15 + expect(contentRange.end, 15); + expect(contentRange.size, fileContent.length); + }, + ); }); group('Given empty Range requests', () { - test( - 'Given a Range header with no ranges, ' + test('Given a Range header with no ranges, ' 'when a request is made for the file, ' 'then an InvalidHeaderException is thrown', () async { final headers = Headers.build((final mh) => mh['Range'] = ['bytes=']); diff --git a/test/static/range_test.dart b/test/static/range_test.dart index 93699cc9..34b2d5c6 100644 --- a/test/static/range_test.dart +++ b/test/static/range_test.dart @@ -12,121 +12,159 @@ void main() { setUp(() async { await d.file('test_file.txt', fileContent).create(); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; }); group('Given a single byte range request', () { test( - 'Given a byte range (start-end), ' - 'when a request is made for the file, ' - 'then a 206 Partial Content status with partial content and Content-Range is returned', - () async { - final headers = Headers.build((final mh) => mh.range = - RangeHeader(ranges: [Range(start: 0, end: 4)])); // 0-4 (5 bytes) - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 5); - expect(response.readAsString(), completion(fileContent.substring(0, 5))); - final contentRange = response.headers.contentRange; - expect(contentRange, isNotNull); - expect(contentRange!.unit, 'bytes'); - expect(contentRange.start, 0); - expect(contentRange.end, 4); - expect(response.headers.contentRange!.size, fileContent.length); - }); + 'Given a byte range (start-end), ' + 'when a request is made for the file, ' + 'then a 206 Partial Content status with partial content and Content-Range is returned', + () async { + final headers = Headers.build( + (final mh) => + mh.range = RangeHeader(ranges: [Range(start: 0, end: 4)]), + ); // 0-4 (5 bytes) + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 5); + expect( + response.readAsString(), + completion(fileContent.substring(0, 5)), + ); + final contentRange = response.headers.contentRange; + expect(contentRange, isNotNull); + expect(contentRange!.unit, 'bytes'); + expect(contentRange.start, 0); + expect(contentRange.end, 4); + expect(response.headers.contentRange!.size, fileContent.length); + }, + ); test( - 'Given a byte range (start-), ' - 'when a request is made for the file, ' - 'then a 206 Partial Content status with partial content and Content-Range is returned', - () async { - final headers = Headers.build((final mh) => mh.range = - RangeHeader(ranges: [Range(start: 10)])); // 10-END (6 bytes) - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 6); - expect(response.readAsString(), completion(fileContent.substring(10))); - expect(response.headers.contentRange, isNotNull); - expect(response.headers.contentRange!.unit, 'bytes'); - expect(response.headers.contentRange!.start, 10); - expect(response.headers.contentRange!.end, fileContent.length - 1); - expect(response.headers.contentRange!.size, fileContent.length); - }); + 'Given a byte range (start-), ' + 'when a request is made for the file, ' + 'then a 206 Partial Content status with partial content and Content-Range is returned', + () async { + final headers = Headers.build( + (final mh) => mh.range = RangeHeader(ranges: [Range(start: 10)]), + ); // 10-END (6 bytes) + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 6); + expect(response.readAsString(), completion(fileContent.substring(10))); + expect(response.headers.contentRange, isNotNull); + expect(response.headers.contentRange!.unit, 'bytes'); + expect(response.headers.contentRange!.start, 10); + expect(response.headers.contentRange!.end, fileContent.length - 1); + expect(response.headers.contentRange!.size, fileContent.length); + }, + ); test( - 'Given a byte range (-suffixLength), ' - 'when a request is made for the file, ' - 'then a 206 Partial Content status with partial content and Content-Range is returned', - () async { - final headers = Headers.build((final mh) => - mh.range = RangeHeader(ranges: [Range(end: 5)])); // last 5 bytes - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - expect(response.body.contentLength, 5); - expect(response.readAsString(), - completion(fileContent.substring(fileContent.length - 5))); - expect(response.headers.contentRange, isNotNull); - expect(response.headers.contentRange!.unit, 'bytes'); - expect(response.headers.contentRange!.start, fileContent.length - 5); - expect(response.headers.contentRange!.end, fileContent.length - 1); - expect(response.headers.contentRange!.size, fileContent.length); - }); + 'Given a byte range (-suffixLength), ' + 'when a request is made for the file, ' + 'then a 206 Partial Content status with partial content and Content-Range is returned', + () async { + final headers = Headers.build( + (final mh) => mh.range = RangeHeader(ranges: [Range(end: 5)]), + ); // last 5 bytes + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + expect(response.body.contentLength, 5); + expect( + response.readAsString(), + completion(fileContent.substring(fileContent.length - 5)), + ); + expect(response.headers.contentRange, isNotNull); + expect(response.headers.contentRange!.unit, 'bytes'); + expect(response.headers.contentRange!.start, fileContent.length - 5); + expect(response.headers.contentRange!.end, fileContent.length - 1); + expect(response.headers.contentRange!.size, fileContent.length); + }, + ); }); group('Given a multiple byte range request', () { test( - 'Given a multiple byte range request, ' - 'when a request is made for the file, ' - 'then a 206 Partial Content status is returned with multipart body', - () async { - // Content: '0123456789ABCDEF' (length 16) - // Ranges: bytes=0-0,2-3,14- - // Expected parts: '0', '23', 'EF' - final headers = Headers.build((final mh) => mh.range = RangeHeader( - ranges: [ - Range(start: 0, end: 0), // '0' - Range(start: 2, end: 3), // '23' - Range(start: 14), // 'EF' - ], - )); - - final response = - await makeRequest(handler, '/test_file.txt', headers: headers); - - expect(response.statusCode, HttpStatus.partialContent); - final mimeType = response.body.bodyType!.mimeType; - expect(mimeType.primaryType, 'multipart'); - expect(mimeType.subType, 'byteranges'); - - final boundary = - _extractBoundary(response.headers[Headers.contentTypeHeader]!.first); - - final bodyString = await response.readAsString(); - - expect( + 'Given a multiple byte range request, ' + 'when a request is made for the file, ' + 'then a 206 Partial Content status is returned with multipart body', + () async { + // Content: '0123456789ABCDEF' (length 16) + // Ranges: bytes=0-0,2-3,14- + // Expected parts: '0', '23', 'EF' + final headers = Headers.build( + (final mh) => + mh.range = RangeHeader( + ranges: [ + Range(start: 0, end: 0), // '0' + Range(start: 2, end: 3), // '23' + Range(start: 14), // 'EF' + ], + ), + ); + + final response = await makeRequest( + handler, + '/test_file.txt', + headers: headers, + ); + + expect(response.statusCode, HttpStatus.partialContent); + final mimeType = response.body.bodyType!.mimeType; + expect(mimeType.primaryType, 'multipart'); + expect(mimeType.subType, 'byteranges'); + + final boundary = _extractBoundary( + response.headers[Headers.contentTypeHeader]!.first, + ); + + final bodyString = await response.readAsString(); + + expect( bodyString, contains( - '--$boundary\r\nContent-Type: text/plain\r\nContent-Range: bytes 0-0/${fileContent.length}\r\n\r\n0\r\n')); - expect( + '--$boundary\r\nContent-Type: text/plain\r\nContent-Range: bytes 0-0/${fileContent.length}\r\n\r\n0\r\n', + ), + ); + expect( bodyString, contains( - '--$boundary\r\nContent-Type: text/plain\r\nContent-Range: bytes 2-3/${fileContent.length}\r\n\r\n23\r\n')); - expect( + '--$boundary\r\nContent-Type: text/plain\r\nContent-Range: bytes 2-3/${fileContent.length}\r\n\r\n23\r\n', + ), + ); + expect( bodyString, contains( - '--$boundary\r\nContent-Type: text/plain\r\nContent-Range: bytes 14-15/${fileContent.length}\r\n\r\nEF\r\n')); - expect(bodyString, endsWith('--$boundary--\r\n')); - }); + '--$boundary\r\nContent-Type: text/plain\r\nContent-Range: bytes 14-15/${fileContent.length}\r\n\r\nEF\r\n', + ), + ); + expect(bodyString, endsWith('--$boundary--\r\n')); + }, + ); }); } @@ -145,8 +183,10 @@ String? _extractBoundary(final String contentType) { } // Extract the boundary string itself and remove any surrounding quotes - final boundary = - boundaryPart.trim().substring('boundary='.length).replaceAll('"', ''); + final boundary = boundaryPart + .trim() + .substring('boundary='.length) + .replaceAll('"', ''); return boundary; } diff --git a/test/static/symbolic_link_test.dart b/test/static/symbolic_link_test.dart index fc44b071..bc5899b5 100644 --- a/test/static/symbolic_link_test.dart +++ b/test/static/symbolic_link_test.dart @@ -21,9 +21,7 @@ void main() { // link_dir -> originals/ await d.dir('', [ - d.dir('originals', [ - d.file('index.html', ''), - ]), + d.dir('originals', [d.file('index.html', '')]), d.dir('alt_root'), ]).create(); @@ -34,32 +32,35 @@ void main() { Link(p.join(d.sandbox, 'link_dir')).createSync(originalsDir); - Link(p.join(d.sandbox, 'alt_root', 'link_index.html')) - .createSync(originalsIndex); + Link( + p.join(d.sandbox, 'alt_root', 'link_index.html'), + ).createSync(originalsIndex); Link(p.join(d.sandbox, 'alt_root', 'link_dir')).createSync(originalsDir); }); group('Given links pointing inside root dir', () { - test( - 'when accessing a sym linked file in a real dir, ' - 'then it returns the file content', - () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; - - final response = await makeRequest(handler, '/link_index.html'); - expect(response.statusCode, HttpStatus.ok); - expect(response.body.contentLength, 13); - expect(response.readAsString(), completion('')); - }, - ); - - test( - 'when accessing a file in a sym linked dir, ' + test('when accessing a sym linked file in a real dir, ' 'then it returns the file content', () async { - final handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; + + final response = await makeRequest(handler, '/link_index.html'); + expect(response.statusCode, HttpStatus.ok); + expect(response.body.contentLength, 13); + expect(response.readAsString(), completion('')); + }); + + test('when accessing a file in a sym linked dir, ' + 'then it returns the file content', () async { + final handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest(handler, '/link_dir/index.html'); expect(response.statusCode, HttpStatus.ok); @@ -69,23 +70,25 @@ void main() { }); group('Given links pointing out of root dir', () { - test( - 'when accessing a sym linked file in a real dir, ' + test('when accessing a sym linked file in a real dir, ' 'then it returns a 404', () async { - final handler = StaticHandler.directory( - Directory(p.join(d.sandbox, 'alt_root')), - cacheControl: (final _, final __) => null).asHandler; + final handler = + StaticHandler.directory( + Directory(p.join(d.sandbox, 'alt_root')), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest(handler, '/link_index.html'); expect(response.statusCode, HttpStatus.notFound); }); - test( - 'when accessing a real file in a sym linked dir, ' + test('when accessing a real file in a sym linked dir, ' 'then it returns a 404', () async { - final handler = StaticHandler.directory( - Directory(p.join(d.sandbox, 'alt_root')), - cacheControl: (final _, final __) => null).asHandler; + final handler = + StaticHandler.directory( + Directory(p.join(d.sandbox, 'alt_root')), + cacheControl: (_, _) => null, + ).asHandler; final response = await makeRequest(handler, '/link_dir/index.html'); expect(response.statusCode, HttpStatus.notFound); diff --git a/test/static/test_util.dart b/test/static/test_util.dart index 76b4458e..602ae8ab 100644 --- a/test/static/test_util.dart +++ b/test/static/test_util.dart @@ -27,12 +27,7 @@ Request _fromPath( final String path, final Headers? headers, { required final Method method, -}) => - Request( - method, - Uri.parse('http://localhost$path'), - headers: headers, - ); +}) => Request(method, Uri.parse('http://localhost$path'), headers: headers); Handler _rootHandler(final String? path, final Handler handler) { if (path == null || path.isEmpty) { @@ -43,11 +38,7 @@ Handler _rootHandler(final String? path, final Handler handler) { final ctx = requestCtx as RespondableContext; final request = ctx.request; if (!_ctx.isWithin('/$path', request.requestedUri.path)) { - return ctx.respond(Response.notFound( - body: Body.fromString( - 'not found', - ), - )); + return ctx.respond(Response.notFound(body: Body.fromString('not found'))); } assert(request.handlerPath == '/'); @@ -64,7 +55,7 @@ class _SecondResolutionDateTimeMatcher extends Matcher { final DateTime _target; _SecondResolutionDateTimeMatcher(final DateTime target) - : _target = target.toSecondResolution; + : _target = target.toSecondResolution; @override bool matches(final dynamic item, final Map matchState) { @@ -74,9 +65,10 @@ class _SecondResolutionDateTimeMatcher extends Matcher { } @override - Description describe(final Description description) => - description.add('Must be at the same moment as $_target with resolution ' - 'to the second.'); + Description describe(final Description description) => description.add( + 'Must be at the same moment as $_target with resolution ' + 'to the second.', + ); } bool _datesEqualToSecond(final DateTime d1, final DateTime d2) => diff --git a/test/static/unsupported_methods_test.dart b/test/static/unsupported_methods_test.dart index c124244e..1917d70c 100644 --- a/test/static/unsupported_methods_test.dart +++ b/test/static/unsupported_methods_test.dart @@ -12,97 +12,104 @@ void main() { setUp(() async { await d.file('test_file.txt', fileContent).create(); - handler = StaticHandler.directory(Directory(d.sandbox), - cacheControl: (final _, final __) => null).asHandler; + handler = + StaticHandler.directory( + Directory(d.sandbox), + cacheControl: (_, _) => null, + ).asHandler; }); group('Given unsupported HTTP methods', () { test( - 'Given a POST request to a static file, ' - 'when the request is made, ' - 'then a 405 Method Not Allowed status is returned with Allow header', - () async { - final response = await makeRequest( - handler, - '/test_file.txt', - method: Method.post, - ); - - expect(response.statusCode, HttpStatus.methodNotAllowed); - expect(response.body.contentLength, 0); - expect(await response.readAsString(), isEmpty); - - final allowHeader = response.headers.allow; - expect(allowHeader, isNotNull); - expect(allowHeader, contains(Method.get)); - expect(allowHeader, contains(Method.head)); - expect(allowHeader!.length, 2); - }); + 'Given a POST request to a static file, ' + 'when the request is made, ' + 'then a 405 Method Not Allowed status is returned with Allow header', + () async { + final response = await makeRequest( + handler, + '/test_file.txt', + method: Method.post, + ); + + expect(response.statusCode, HttpStatus.methodNotAllowed); + expect(response.body.contentLength, 0); + expect(await response.readAsString(), isEmpty); + + final allowHeader = response.headers.allow; + expect(allowHeader, isNotNull); + expect(allowHeader, contains(Method.get)); + expect(allowHeader, contains(Method.head)); + expect(allowHeader!.length, 2); + }, + ); test( - 'Given a PUT request to a static file, ' - 'when the request is made, ' - 'then a 405 Method Not Allowed status is returned with Allow header', - () async { - final response = await makeRequest( - handler, - '/test_file.txt', - method: Method.put, - ); - - expect(response.statusCode, HttpStatus.methodNotAllowed); - expect(response.body.contentLength, 0); - expect(await response.readAsString(), isEmpty); - - final allowHeader = response.headers.allow; - expect(allowHeader, isNotNull); - expect(allowHeader, contains(Method.get)); - expect(allowHeader, contains(Method.head)); - expect(allowHeader!.length, 2); - }); + 'Given a PUT request to a static file, ' + 'when the request is made, ' + 'then a 405 Method Not Allowed status is returned with Allow header', + () async { + final response = await makeRequest( + handler, + '/test_file.txt', + method: Method.put, + ); + + expect(response.statusCode, HttpStatus.methodNotAllowed); + expect(response.body.contentLength, 0); + expect(await response.readAsString(), isEmpty); + + final allowHeader = response.headers.allow; + expect(allowHeader, isNotNull); + expect(allowHeader, contains(Method.get)); + expect(allowHeader, contains(Method.head)); + expect(allowHeader!.length, 2); + }, + ); test( - 'Given a DELETE request to a static file, ' - 'when the request is made, ' - 'then a 405 Method Not Allowed status is returned with Allow header', - () async { - final response = await makeRequest( - handler, - '/test_file.txt', - method: Method.delete, - ); - - expect(response.statusCode, HttpStatus.methodNotAllowed); - expect(response.body.contentLength, 0); - expect(await response.readAsString(), isEmpty); - - final allowHeader = response.headers.allow; - expect(allowHeader, isNotNull); - expect(allowHeader, contains(Method.get)); - expect(allowHeader, contains(Method.head)); - expect(allowHeader!.length, 2); - }); + 'Given a DELETE request to a static file, ' + 'when the request is made, ' + 'then a 405 Method Not Allowed status is returned with Allow header', + () async { + final response = await makeRequest( + handler, + '/test_file.txt', + method: Method.delete, + ); + + expect(response.statusCode, HttpStatus.methodNotAllowed); + expect(response.body.contentLength, 0); + expect(await response.readAsString(), isEmpty); + + final allowHeader = response.headers.allow; + expect(allowHeader, isNotNull); + expect(allowHeader, contains(Method.get)); + expect(allowHeader, contains(Method.head)); + expect(allowHeader!.length, 2); + }, + ); test( - 'Given an OPTIONS request to a static file, ' - 'when the request is made, ' - 'then a 405 Method Not Allowed status is returned with Allow header', - () async { - final response = await makeRequest( - handler, - '/test_file.txt', - method: Method.options, - ); - - expect(response.statusCode, HttpStatus.methodNotAllowed); - expect(response.body.contentLength, 0); - expect(await response.readAsString(), isEmpty); - - final allowHeader = response.headers.allow; - expect(allowHeader, isNotNull); - expect(allowHeader, contains(Method.get)); - expect(allowHeader, contains(Method.head)); - expect(allowHeader!.length, 2); - }); + 'Given an OPTIONS request to a static file, ' + 'when the request is made, ' + 'then a 405 Method Not Allowed status is returned with Allow header', + () async { + final response = await makeRequest( + handler, + '/test_file.txt', + method: Method.options, + ); + + expect(response.statusCode, HttpStatus.methodNotAllowed); + expect(response.body.contentLength, 0); + expect(await response.readAsString(), isEmpty); + + final allowHeader = response.headers.allow; + expect(allowHeader, isNotNull); + expect(allowHeader, contains(Method.get)); + expect(allowHeader, contains(Method.head)); + expect(allowHeader!.length, 2); + }, + ); }); } diff --git a/test/util/test_util.dart b/test/util/test_util.dart index b6f93123..d03dba6f 100644 --- a/test/util/test_util.dart +++ b/test/util/test_util.dart @@ -24,12 +24,15 @@ SyncHandler createSyncHandler({ final Body? body, }) { return (final NewContext ctx) { - return ctx.respond(Response( - statusCode, - headers: headers ?? Headers.empty(), - body: body ?? - Body.fromString('Hello from ${ctx.request.requestedUri.path}'), - )); + return ctx.respond( + Response( + statusCode, + headers: headers ?? Headers.empty(), + body: + body ?? + Body.fromString('Hello from ${ctx.request.requestedUri.path}'), + ), + ); }; } @@ -41,10 +44,13 @@ Future asyncHandler(final NewContext ctx) async { } /// Makes a simple GET request to [handler] and returns the result. -Future makeSimpleRequest(final Handler handler, - [final Request? request]) async { - final newCtx = - await handler((request ?? _defaultRequest).toContext(Object())); +Future makeSimpleRequest( + final Handler handler, [ + final Request? request, +]) async { + final newCtx = await handler( + (request ?? _defaultRequest).toContext(Object()), + ); if (newCtx is! ResponseContext) throw ArgumentError(newCtx); return newCtx.response; } @@ -53,18 +59,20 @@ final _defaultRequest = Request(Method.get, localhostUri); final localhostUri = Uri.parse('http://localhost/'); -final isOhNoStateError = - isA().having((final e) => e.message, 'message', 'oh no'); +final isOhNoStateError = isA().having( + (final e) => e.message, + 'message', + 'oh no', +); Future testServe( final Handler handler, { final SecurityContext? context, }) async { - final server = RelicServer(() => IOAdapter.bind( - InternetAddress.loopbackIPv4, - port: 0, - context: context, - )); + final server = RelicServer( + () => + IOAdapter.bind(InternetAddress.loopbackIPv4, port: 0, context: context), + ); await server.mountAndStart(handler); return server; } diff --git a/test/util/util_test.dart b/test/util/util_test.dart index d6708a9c..dc8090f3 100644 --- a/test/util/util_test.dart +++ b/test/util/util_test.dart @@ -4,22 +4,22 @@ import 'package:relic/src/util/util.dart'; import 'package:test/test.dart'; void main() { - test( - 'Given a Sink created with mapFrom, ' + test('Given a Sink created with mapFrom, ' 'when adding events, ' 'then the mapped event is added to target sink', () { final controller = StreamController(); final sink = controller.mapFrom(int.parse); expectLater( - controller.stream, - emitsInOrder([ - 42, - emitsError(isA()), - emitsError(isA()), - 1202, - emitsDone - ])); + controller.stream, + emitsInOrder([ + 42, + emitsError(isA()), + emitsError(isA()), + 1202, + emitsDone, + ]), + ); sink.add('42'); sink.add('not a number'); diff --git a/test/web_socket/web_socket_test.dart b/test/web_socket/web_socket_test.dart index c0bd29fc..68788e05 100644 --- a/test/web_socket/web_socket_test.dart +++ b/test/web_socket/web_socket_test.dart @@ -21,10 +21,7 @@ Future scheduleServer( final io.SecurityContext? securityContext, }) async { await _server?.close(); // close previous, if any - _server = await testServe( - handler, - context: securityContext, - ); + _server = await testServe(handler, context: securityContext); } void main() { @@ -35,197 +32,230 @@ void main() { group('WebSocket Tests', () { test( - 'Given a WebSocket server, ' - 'when a client connects and sends "tick", ' - 'then the server responds with "tock" and the client receives it', - () async { - await scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) async { - // Expect a message from the client - await for (final e in serverSocket.events) { - expect(e, TextDataReceived('tick')); - // Send a message back to the client - serverSocket.sendText('tock'); - // Close the server-side of the connection after sending the message. - // This also signals the client that no more messages are coming from the server. - await serverSocket.close(); - } - })); - }); - - final clientSocket = - await WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')); - - // Send a message to the server - clientSocket.sendText('tick'); + 'Given a WebSocket server, ' + 'when a client connects and sends "tick", ' + 'then the server responds with "tock" and the client receives it', + () async { + await scheduleServer((final ctx) { + return ctx.connect( + expectAsync1((final serverSocket) async { + // Expect a message from the client + await for (final e in serverSocket.events) { + expect(e, TextDataReceived('tick')); + // Send a message back to the client + serverSocket.sendText('tock'); + // Close the server-side of the connection after sending the message. + // This also signals the client that no more messages are coming from the server. + await serverSocket.close(); + } + }), + ); + }); - // Expect a response from the server - await expectLater( - clientSocket.events, - emitsInOrder([ - TextDataReceived('tock'), - CloseReceived(1005), - emitsDone, - ]), - ); - }); + final clientSocket = await WebSocket.connect( + Uri.parse('ws://localhost:$_serverPort'), + ); + + // Send a message to the server + clientSocket.sendText('tick'); + + // Expect a response from the server + await expectLater( + clientSocket.events, + emitsInOrder([ + TextDataReceived('tock'), + CloseReceived(1005), + emitsDone, + ]), + ); + }, + ); - test( - 'Given a WebSocket server, ' + test('Given a WebSocket server, ' 'when a client sends multiple messages, ' 'then the server receives all messages in order', () async { final serverReceivedMessages = []; await scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) { - serverSocket.events.listen( - (final message) async { + return ctx.connect( + expectAsync1((final serverSocket) { + serverSocket.events.listen((final message) async { if (message is TextDataReceived) { serverReceivedMessages.add(message.text); if (serverReceivedMessages.length == 2) { await serverSocket.close(); } } - }, - ); - })); + }); + }), + ); }); - final clientSocket = - await WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')); + final clientSocket = await WebSocket.connect( + Uri.parse('ws://localhost:$_serverPort'), + ); clientSocket.sendText('msg1'); clientSocket.sendText('msg2'); await expectLater( - clientSocket.events, emitsInOrder([CloseReceived(1005), emitsDone])); + clientSocket.events, + emitsInOrder([CloseReceived(1005), emitsDone]), + ); expect(serverReceivedMessages, equals(['msg1', 'msg2'])); }); test( - 'Given a WebSocket server that sends multiple messages upon client connection, ' - 'when a client connects and sends an initial message, ' - 'then the client receives all server messages in order', () async { - await scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) async { - serverSocket.sendText('tock1'); - serverSocket.sendText('tock2'); - })); - }); + 'Given a WebSocket server that sends multiple messages upon client connection, ' + 'when a client connects and sends an initial message, ' + 'then the client receives all server messages in order', + () async { + await scheduleServer((final ctx) { + return ctx.connect( + expectAsync1((final serverSocket) async { + serverSocket.sendText('tock1'); + serverSocket.sendText('tock2'); + }), + ); + }); - final clientSocket = - await WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')); + final clientSocket = await WebSocket.connect( + Uri.parse('ws://localhost:$_serverPort'), + ); - await expectLater( - clientSocket.events, - emitsInOrder(['tock1', 'tock2'].map(TextDataReceived.new)), - ); - await clientSocket.close(); - }); + await expectLater( + clientSocket.events, + emitsInOrder(['tock1', 'tock2'].map(TextDataReceived.new)), + ); + await clientSocket.close(); + }, + ); // This test is mostly a proof for why you should never use // WebSocketChannel client-side. - test( - 'Given a server that does not upgrade to WebSocket, ' + test('Given a server that does not upgrade to WebSocket, ' 'when a client using WebSocketChannel.connect attempts to send a message, ' 'then a WebSocketChannelException occurs', () async { - await scheduleServer(respondWith((final _) { - // Intentionally do nothing to handle the WebSocket upgrade request - return Response.notFound( - body: Body.fromString('Not a WebSocket endpoint')); - })); + await scheduleServer( + respondWith((_) { + // Intentionally do nothing to handle the WebSocket upgrade request + return Response.notFound( + body: Body.fromString('Not a WebSocket endpoint'), + ); + }), + ); // Attempt to send a message should cause a failure final done = Completer(); var endOfScope = false; - await runZonedGuarded(() async { - // We use runZoneGuarded to capture the unhandled exception. - // There are unfortunately nowhere we can use await to - // capture the error. - final wsUri = Uri.parse('ws://localhost:$_serverPort'); - final channel = WebSocketChannel.connect(wsUri); // <-- why not async!! - // Waiting for ready is easy to forget - // await channel.ready; // <-- will raise - channel.sink.add('tick'); - // Now we have caused an unhandled error from an un-awaited future, - // which is really annoying and require runZoneGuarded to capture. - endOfScope = true; // to prove we make it here without error - }, (final e, final _) { - expect(e, isA()); - done.complete(); - }); + await runZonedGuarded( + () async { + // We use runZoneGuarded to capture the unhandled exception. + // There are unfortunately nowhere we can use await to + // capture the error. + final wsUri = Uri.parse('ws://localhost:$_serverPort'); + final channel = WebSocketChannel.connect( + wsUri, + ); // <-- why not async!! + // Waiting for ready is easy to forget + // await channel.ready; // <-- will raise + channel.sink.add('tick'); + // Now we have caused an unhandled error from an un-awaited future, + // which is really annoying and require runZoneGuarded to capture. + endOfScope = true; // to prove we make it here without error + }, + (final e, _) { + expect(e, isA()); + done.complete(); + }, + ); await done.future; // wait for error to be captured - expect(endOfScope, isTrue, - reason: 'Sanity check that test reached end of scope'); + expect( + endOfScope, + isTrue, + reason: 'Sanity check that test reached end of scope', + ); }); - test( - 'Given a server that does not upgrade to WebSocket, ' + test('Given a server that does not upgrade to WebSocket, ' 'when a client uses WebSocket.connect, ' 'then the connection attempt throws a WebSocketException', () async { - await scheduleServer(respondWith((final _) { - // Intentionally do nothing to handle the WebSocket upgrade request - return Response.notFound( - body: Body.fromString('Not a WebSocket endpoint')); - })); + await scheduleServer( + respondWith((_) { + // Intentionally do nothing to handle the WebSocket upgrade request + return Response.notFound( + body: Body.fromString('Not a WebSocket endpoint'), + ); + }), + ); // Attempt to send a message should cause a failure - expect(WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')), - throwsA(isA())); // <-- this is the way!! + expect( + WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')), + throwsA(isA()), + ); // <-- this is the way!! }); test( - 'Given a WebSocket server, ' - 'when a client sends a binary message, ' - 'then the server processes it and sends a binary response which the client receives', - () async { - final binaryData = - Uint8List.fromList(List.generate(10, (final i) => i)); - final responseBinaryData = - Uint8List.fromList(List.generate(10, (final i) => i * 2)); + 'Given a WebSocket server, ' + 'when a client sends a binary message, ' + 'then the server processes it and sends a binary response which the client receives', + () async { + final binaryData = Uint8List.fromList( + List.generate(10, (final i) => i), + ); + final responseBinaryData = Uint8List.fromList( + List.generate(10, (final i) => i * 2), + ); - await scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) async { - final message = await serverSocket.events.first; - expect(message, isA()); - expect((message as BinaryDataReceived).data, equals(binaryData)); - - serverSocket.sendBytes(responseBinaryData); - await serverSocket.close(); - })); - }); + await scheduleServer((final ctx) { + return ctx.connect( + expectAsync1((final serverSocket) async { + final message = await serverSocket.events.first; + expect(message, isA()); + expect((message as BinaryDataReceived).data, equals(binaryData)); + + serverSocket.sendBytes(responseBinaryData); + await serverSocket.close(); + }), + ); + }); - final clientSocket = - await WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')); + final clientSocket = await WebSocket.connect( + Uri.parse('ws://localhost:$_serverPort'), + ); - clientSocket.sendBytes(binaryData); + clientSocket.sendBytes(binaryData); - await expectLater( - clientSocket.events, - emitsInOrder([ - BinaryDataReceived(responseBinaryData), - CloseReceived(1005), - emitsDone - ]), - ); - }); + await expectLater( + clientSocket.events, + emitsInOrder([ + BinaryDataReceived(responseBinaryData), + CloseReceived(1005), + emitsDone, + ]), + ); + }, + ); - test( - 'Given a WebSocket server, ' + test('Given a WebSocket server, ' 'when a client connects and then closes the connection, ' 'then the server-side events stream completes', () async { final serverSocketClosed = Completer(); await scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) async { - // Wait for the client to close the connection by - // consuming the stream until it's done - await serverSocket.events.drain(null); - serverSocketClosed.complete(); - })); + return ctx.connect( + expectAsync1((final serverSocket) async { + // Wait for the client to close the connection by + // consuming the stream until it's done + await serverSocket.events.drain(null); + serverSocketClosed.complete(); + }), + ); }); - final clientSocket = - await WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')); + final clientSocket = await WebSocket.connect( + Uri.parse('ws://localhost:$_serverPort'), + ); // Client closes the connection unawaited(clientSocket.close()); @@ -234,80 +264,80 @@ void main() { expect(serverSocketClosed.future, completes); }); - test( - 'Given a WebSocket server that closes the connection immediately, ' + test('Given a WebSocket server that closes the connection immediately, ' 'when a client connects, ' 'then the client-side stream completes', () async { await scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) async { - // Server immediately closes the connection - await serverSocket.close(); - })); + return ctx.connect( + expectAsync1((final serverSocket) async { + // Server immediately closes the connection + await serverSocket.close(); + }), + ); }); - final clientSocket = - await WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')); + final clientSocket = await WebSocket.connect( + Uri.parse('ws://localhost:$_serverPort'), + ); // Expect the client-side stream to complete because the server closed it await expectLater( - clientSocket.events, emitsInOrder([CloseReceived(1005), emitsDone])); + clientSocket.events, + emitsInOrder([CloseReceived(1005), emitsDone]), + ); }); test( - 'Given a web socket connection with a ping interval, ' - 'when a client connects and remains idle for a period, ' - 'then the connection is maintained by pings and subsequent communication is successful', - () async { - const pingInterval = Duration(milliseconds: 5); - final tooLong = pingInterval * 3; // Must be > pingInterval + 'Given a web socket connection with a ping interval, ' + 'when a client connects and remains idle for a period, ' + 'then the connection is maintained by pings and subsequent communication is successful', + () async { + const pingInterval = Duration(milliseconds: 5); + final tooLong = pingInterval * 3; // Must be > pingInterval - await scheduleServer( - (final ctx) { - return ctx.connect( - (final serverSocket) async { - serverSocket.pingInterval = pingInterval; - await for (final e in serverSocket.events) { - if (e is CloseReceived) break; - expect(e, TextDataReceived('tick')); - // Server remains idle for a period, relying on pings to keep connection alive. - await Future.delayed(tooLong); - serverSocket.sendText('tock'); - } - }, - ); - }, - ); + await scheduleServer((final ctx) { + return ctx.connect((final serverSocket) async { + serverSocket.pingInterval = pingInterval; + await for (final e in serverSocket.events) { + if (e is CloseReceived) break; + expect(e, TextDataReceived('tick')); + // Server remains idle for a period, relying on pings to keep connection alive. + await Future.delayed(tooLong); + serverSocket.sendText('tock'); + } + }); + }); - final clientSocket = - await WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')); + final clientSocket = await WebSocket.connect( + Uri.parse('ws://localhost:$_serverPort'), + ); - // Client remains idle for a period, relying on pings to keep connection alive. - await Future.delayed(tooLong); - clientSocket.sendText('tick'); + // Client remains idle for a period, relying on pings to keep connection alive. + await Future.delayed(tooLong); + clientSocket.sendText('tick'); - await expectLater(clientSocket.events, emits(TextDataReceived('tock'))); - }); + await expectLater(clientSocket.events, emits(TextDataReceived('tock'))); + }, + ); - test( - 'Given a web socket connection with a ping interval, ' - 'when the server side disappear, ' - 'then client socket closes', - () async { - const pingInterval = Duration(milliseconds: 15); - - // Setup wait points, signalled from isolate - final port = Completer(); - final ready = Completer(); - final killed = Completer(); - final completers = [port, ready, killed]; - final recv = ReceivePort(); - int idx = 0; - recv.listen((final e) { - // Signal received! Update associated completer - completers[idx++].complete(e); - }); + test('Given a web socket connection with a ping interval, ' + 'when the server side disappear, ' + 'then client socket closes', () async { + const pingInterval = Duration(milliseconds: 15); + + // Setup wait points, signalled from isolate + final port = Completer(); + final ready = Completer(); + final killed = Completer(); + final completers = [port, ready, killed]; + final recv = ReceivePort(); + int idx = 0; + recv.listen((final e) { + // Signal received! Update associated completer + completers[idx++].complete(e); + }); - final isolate = await Isolate.spawn((final sendPort) async { + final isolate = await Isolate.spawn((final sendPort) async { final server = await testServe((final ctx) { return ctx.connect((final serverSocket) async { serverSocket.sendText('running'); @@ -316,113 +346,123 @@ void main() { }); sendPort.send(server.url.port); // signal port }, recv.sendPort) - ..addOnExitListener(recv.sendPort, response: true); // signal killed + ..addOnExitListener(recv.sendPort, response: true); // signal killed - final clientSocket = await IORelicWebSocket.connect( - Uri.parse('ws://localhost:${await port.future}')); - clientSocket.pingInterval = pingInterval; + final clientSocket = await IORelicWebSocket.connect( + Uri.parse('ws://localhost:${await port.future}'), + ); + clientSocket.pingInterval = pingInterval; - final check = expectLater( - clientSocket.events, - emitsInOrder([ - TextDataReceived('running'), - CloseReceived(1001), - emitsDone, - ])); + final check = expectLater( + clientSocket.events, + emitsInOrder([ + TextDataReceived('running'), + CloseReceived(1001), + emitsDone, + ]), + ); - await ready.future; + await ready.future; - isolate.kill(); - await killed.future; + isolate.kill(); + await killed.future; - await check; - }, - ); + await check; + }); - test( - 'Given a web socket connection with a ping interval, ' - 'when the client side blocks, ' - 'then the server socket closes', - () async { - const pingInterval = Duration(milliseconds: 5); - final done = Completer(); + test('Given a web socket connection with a ping interval, ' + 'when the client side blocks, ' + 'then the server socket closes', () async { + const pingInterval = Duration(milliseconds: 5); + final done = Completer(); - await scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) async { + await scheduleServer((final ctx) { + return ctx.connect( + expectAsync1((final serverSocket) async { serverSocket.pingInterval = pingInterval; expect(serverSocket.pingInterval, pingInterval); await expectLater( - serverSocket.events, - emitsInOrder([ - TextDataReceived('running'), - CloseReceived(1001), // 1001 indicates normal closure?! - emitsDone, - ])); + serverSocket.events, + emitsInOrder([ + TextDataReceived('running'), + CloseReceived(1001), // 1001 indicates normal closure?! + emitsDone, + ]), + ); done.complete(); - })); - }); + }), + ); + }); - final isolate = await Isolate.spawn((final port) async { - final clientSocket = - await WebSocket.connect(Uri.parse('ws://localhost:$port')); - clientSocket.sendText('running'); - // no flush, so leave a bit of time before blocking - await Future.delayed(const Duration(milliseconds: 100)); - while (true) {} // busy wait to simulate offline client - }, _serverPort); + final isolate = await Isolate.spawn((final port) async { + final clientSocket = await WebSocket.connect( + Uri.parse('ws://localhost:$port'), + ); + clientSocket.sendText('running'); + // no flush, so leave a bit of time before blocking + await Future.delayed(const Duration(milliseconds: 100)); + while (true) {} // busy wait to simulate offline client + }, _serverPort); - await done.future; + await done.future; - isolate.kill(); - }, - ); + isolate.kill(); + }); }); - test( - 'Given a web socket connection that has been closed, ' + test('Given a web socket connection that has been closed, ' 'when trying to use close, sendText, or sendBytes, ' 'then it throws WebSocketConnectionClosed', () async { await scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) async { - await for (final _ in serverSocket.events) { - expect(serverSocket.close(), _throwsWscClosed); - expect(() => serverSocket.sendText('hello'), _throwsWscClosed); - expect(() => serverSocket.sendBytes(utf8.encode('hello')), - _throwsWscClosed); - expect(serverSocket.protocol, ''); - expect(() => serverSocket.toString(), returnsNormally); - } - })); + return ctx.connect( + expectAsync1((final serverSocket) async { + await for (final _ in serverSocket.events) { + expect(serverSocket.close(), _throwsWscClosed); + expect(() => serverSocket.sendText('hello'), _throwsWscClosed); + expect( + () => serverSocket.sendBytes(utf8.encode('hello')), + _throwsWscClosed, + ); + expect(serverSocket.protocol, ''); + expect(() => serverSocket.toString(), returnsNormally); + } + }), + ); }); - final clientSocket = - await WebSocket.connect(Uri.parse('ws://localhost:$_serverPort')); + final clientSocket = await WebSocket.connect( + Uri.parse('ws://localhost:$_serverPort'), + ); await clientSocket.close(); expect(clientSocket.close(), _throwsWscClosed); expect(() => clientSocket.sendText('hello'), _throwsWscClosed); expect( - () => clientSocket.sendBytes(utf8.encode('hello')), _throwsWscClosed); + () => clientSocket.sendBytes(utf8.encode('hello')), + _throwsWscClosed, + ); expect(clientSocket.events, emitsDone); expect(clientSocket.protocol, ''); expect(() => clientSocket.toString(), returnsNormally); }); - test( - 'Given a web socket connection that has been closed, ' + test('Given a web socket connection that has been closed, ' 'when trying to use tryClose, trySendText, or trySendBytes, ' 'then they return false', () async { await scheduleServer((final ctx) { - return ctx.connect(expectAsync1((final serverSocket) async { - await for (final _ in serverSocket.events) { - expect(serverSocket.tryClose(), completion(isFalse)); - expect(serverSocket.trySendText('hello'), isFalse); - expect(serverSocket.trySendBytes(utf8.encode('hello')), isFalse); - expect(serverSocket.protocol, ''); - expect(() => serverSocket.toString(), returnsNormally); - } - })); + return ctx.connect( + expectAsync1((final serverSocket) async { + await for (final _ in serverSocket.events) { + expect(serverSocket.tryClose(), completion(isFalse)); + expect(serverSocket.trySendText('hello'), isFalse); + expect(serverSocket.trySendBytes(utf8.encode('hello')), isFalse); + expect(serverSocket.protocol, ''); + expect(() => serverSocket.toString(), returnsNormally); + } + }), + ); }); final clientSocket = await IORelicWebSocket.connect( - Uri.parse('ws://localhost:$_serverPort')); + Uri.parse('ws://localhost:$_serverPort'), + ); await clientSocket.close(); expect(clientSocket.tryClose(), completion(isFalse)); expect(clientSocket.trySendText('hello'), isFalse); @@ -432,15 +472,15 @@ void main() { expect(() => clientSocket.toString(), returnsNormally); }); - test( - 'Given a web socket connection, ' + test('Given a web socket connection, ' 'when calling close, ' 'then arguments are validated', () async { await scheduleServer((final ctx) { return ctx.connect(expectAsync1((final serverSocket) async {})); }); final clientSocket = await IORelicWebSocket.connect( - Uri.parse('ws://localhost:$_serverPort')); + Uri.parse('ws://localhost:$_serverPort'), + ); expect(clientSocket.close(1002), throwsArgumentError); expect(clientSocket.close(3000, '-' * 124), throwsArgumentError); }); From 47bd3a59b6df3f48f04b1e89cd639d1de66dd9a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 21 May 2025 07:50:36 +0200 Subject: [PATCH 4/6] feat: Add utilities for working with functions - pipe - compose - pack - apply --- lib/relic.dart | 1 + lib/src/util/functional.dart | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 lib/src/util/functional.dart diff --git a/lib/relic.dart b/lib/relic.dart index 29305c6f..4c38c002 100644 --- a/lib/relic.dart +++ b/lib/relic.dart @@ -29,3 +29,4 @@ export 'src/router/lookup_result.dart'; export 'src/router/method.dart'; export 'src/router/router.dart'; export 'src/router/router_handler_extension.dart'; +export 'src/util/functional.dart'; diff --git a/lib/src/util/functional.dart b/lib/src/util/functional.dart new file mode 100644 index 00000000..64f2f8a5 --- /dev/null +++ b/lib/src/util/functional.dart @@ -0,0 +1,45 @@ +extension Pipe on T { + R pipe(final R Function(T) next) => next(this); +} + +extension Compose on R Function(T) { + R Function(U) compose(final T Function(U) inner) => + (final u) => this(inner(u)); +} + +// === Apply section === +extension Apply1 on R Function(T) { + R apply(final T x) => this(x); +} + +extension Apply2 on R Function(T, U) { + R Function(U) apply(final T t) => (final U u) => this(t, u); +} + +extension Apply3 on R Function(T, U, V) { + R Function(U, V) apply(final T t) => (final U u, final V v) => this(t, u, v); +} + +extension Apply4 on R Function(T, U, V, X) { + R Function(U, V, X) apply(final T t) => + (final U u, final V v, final X x) => this(t, u, v, x); +} + +// === Pack section === +extension Pack1 on R Function(T) { + R Function((T,)) get pack => (final (T,) x) => this(x.$1); +} + +extension Pack2 on R Function(T, U) { + R Function((T, U)) get pack => (final (T, U) x) => this(x.$1, x.$2); +} + +extension Pack3 on R Function(T, U, V) { + R Function((T, U, V)) get pack => + (final (T, U, V) x) => this(x.$1, x.$2, x.$3); +} + +extension Pack4 on R Function(T, U, V, X) { + R Function((T, U, V, X)) get pack => + (final (T, U, V, X) x) => this(x.$1, x.$2, x.$3, x.$4); +} From 4e6e0a08d7fc4eee258f9e5ff4bd9ae4aa9cdeaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 9 Jun 2025 14:42:27 +0200 Subject: [PATCH 5/6] test: Add tests for apply and pack --- test/util/functional_test.dart | 240 +++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 test/util/functional_test.dart diff --git a/test/util/functional_test.dart b/test/util/functional_test.dart new file mode 100644 index 00000000..5d79a521 --- /dev/null +++ b/test/util/functional_test.dart @@ -0,0 +1,240 @@ +import 'package:relic/src/util/functional.dart'; +import 'package:test/test.dart'; + +void main() { + group('Pipe Extension', () { + test( + 'Given a value and a function, ' + 'when the pipe extension is used, ' + 'then the function should be applied to the value', () { + // Arrange + const initialValue = 5; + int addTwo(final int x) => x + 2; + + // Act + final result = initialValue.pipe(addTwo); + + // Assert + expect(result, equals(7)); + }); + + test( + 'Given a string and a sequence of string operations, ' + 'when piped together, ' + 'then the correct final string is produced', () { + // Arrange + const initialValue = 'hello'; + String toUpper(final String s) => s.toUpperCase(); + String addWorld(final String s) => '$s WORLD'; + String exclaim(final String s) => '$s!'; + + // Act + final result = initialValue.pipe(toUpper).pipe(addWorld).pipe(exclaim); + + // Assert + expect(result, equals('HELLO WORLD!')); + }); + }); + + group('Compose Extension', () { + test( + 'Given two functions, ' + 'when they are composed, ' + 'then the resulting function should be their composition', () { + // Arrange + int addTwo(final int x) => x + 2; + int multiplyByThree(final int x) => x * 3; + + // Act + final composedFunction = + multiplyByThree.compose(addTwo); // multiplyByThree(addTwo(x)) + final result = composedFunction( + 5); // multiplyByThree(addTwo(5)) = multiplyByThree(7) = 21 + + // Assert + expect(result, equals(21)); + }); + + test( + 'Given three functions for string manipulation, ' + 'when composed sequentially, ' + 'then the result is the correct transformation', () { + // Arrange + String toUpper(final String s) => s.toUpperCase(); + String addSuffix(final String s) => '${s}_suffix'; + String prefixWith(final String prefix, final String s) => '$prefix$s'; + + String addHelloPrefix(final String s) => prefixWith('hello_', s); + + // Act + // Desired: hello_INPUT_suffix + // addSuffix(toUpper(s)) = INPUT_suffix + // addHelloPrefix(addSuffix(toUpper(s))) = hello_INPUT_suffix + final composed = addHelloPrefix.compose(addSuffix.compose(toUpper)); + final result = composed('input'); + + // Assert + expect(result, equals('hello_INPUT_suffix')); + }); + }); + + group('Apply Extensions', () { + group('Apply1', () { + test( + 'Given a function of one argument, ' + 'when apply is used, ' + 'then the function is called with the argument', () { + // Arrange + int func(final int a) => a * 2; + const arg = 5; + + // Act + final result = func.apply(arg); + + // Assert + expect(result, equals(10)); + }); + }); + + group('Apply2', () { + test( + 'Given a function of two arguments and one argument, ' + 'when apply is used, ' + 'then it should return a function that takes the second argument', + () { + // Arrange + int func(final int a, final int b) => a + b; + const arg1 = 10; + const arg2 = 5; + + // Act + final partiallyApplied = func.apply(arg1); + final result = partiallyApplied(arg2); + + // Assert + expect(result, equals(15)); + }); + }); + + group('Apply3', () { + test( + 'Given a function of three arguments and one argument, ' + 'when apply is used, ' + 'then it should return a function that takes the remaining two arguments', + () { + // Arrange + String func(final String a, final String b, final String c) => + '$a $b $c'; + const arg1 = 'Hello'; + const arg2 = 'World'; + const arg3 = '!'; + + // Act + final partiallyApplied = func.apply(arg1); + final result = partiallyApplied(arg2, arg3); + + // Assert + expect(result, equals('Hello World !')); + }); + }); + + group('Apply4', () { + test( + 'Given a function of four arguments and one argument, ' + 'when apply is used, ' + 'then it should return a function that takes the remaining three arguments', + () { + // Arrange + int func(final int a, final int b, final int c, final int d) => + a + b + c + d; + const arg1 = 1; + const arg2 = 2; + const arg3 = 3; + const arg4 = 4; + + // Act + final partiallyApplied = func.apply(arg1); + final result = partiallyApplied(arg2, arg3, arg4); + + // Assert + expect(result, equals(10)); + }); + }); + }); + + group('Pack Extensions', () { + group('Pack1', () { + test( + 'Given a function of one argument, ' + 'when pack is used, ' + 'then it should return a function that takes a 1-tuple', () { + // Arrange + int func(final int a) => a * 2; + const tuple = (5,); + + // Act + final packedFunc = func.pack; + final result = packedFunc(tuple); + + // Assert + expect(result, equals(10)); + }); + }); + + group('Pack2', () { + test( + 'Given a function of two arguments, ' + 'when pack is used, ' + 'then it should return a function that takes a 2-tuple', () { + // Arrange + int func(final int a, final int b) => a + b; + const tuple = (10, 5); + + // Act + final packedFunc = func.pack; + final result = packedFunc(tuple); + + // Assert + expect(result, equals(15)); + }); + }); + + group('Pack3', () { + test( + 'Given a function of three arguments, ' + 'when pack is used, ' + 'then it should return a function that takes a 3-tuple', () { + // Arrange + String func(final String a, final String b, final String c) => + '$a $b $c'; + const tuple = ('Hello', 'World', '!'); + + // Act + final packedFunc = func.pack; + final result = packedFunc(tuple); + + // Assert + expect(result, equals('Hello World !')); + }); + }); + + group('Pack4', () { + test( + 'Given a function of four arguments, ' + 'when pack is used, ' + 'then it should return a function that takes a 4-tuple', () { + // Arrange + int func(final int a, final int b, final int c, final int d) => + a + b + c + d; + const tuple = (1, 2, 3, 4); + + // Act + final packedFunc = func.pack; + final result = packedFunc(tuple); + + // Assert + expect(result, equals(10)); + }); + }); + }); +} From 92957de53d11f9e5d37277c73e6ca43aeb84ce4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 9 Jun 2025 15:03:12 +0200 Subject: [PATCH 6/6] docs: Add doc comment to pipe, compose, apply, and pack --- lib/src/util/functional.dart | 124 +++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/lib/src/util/functional.dart b/lib/src/util/functional.dart index 64f2f8a5..9395c264 100644 --- a/lib/src/util/functional.dart +++ b/lib/src/util/functional.dart @@ -1,45 +1,169 @@ +/// Extends any type [T] with a `pipe` method for fluent function application. extension Pipe on T { + /// Applies the given function [next] to this value. + /// + /// Returns the result of applying [next] to `this`. + /// This allows for a chainable, readable way to apply a sequence of functions. + /// + /// Example: + /// ```dart + /// final result = 5 + /// .pipe((x) => x + 3) + /// .pipe((x) => x * 2); // result is 16 + /// ``` R pipe(final R Function(T) next) => next(this); } +/// Extends a function type [R Function(T)] with a `compose` method. extension Compose on R Function(T) { + /// Composes this function with an [inner] function. + /// + /// Returns a new function that takes an argument of type [U], + /// first applies the [inner] function ( `T Function(U)` ) to it, + /// and then applies this function ( `R Function(T)` ) to the result. + /// + /// The resulting function has the signature `R Function(U)`. + /// This is equivalent to `g(f(x))` if `this` is `g` and [inner] is `f`. + /// + /// Example: + /// ```dart + /// final intToString = (int i) => i.toString(); + /// final addPrefix = (String s) => 'Number: $s'; + /// final intToPrefixedString = addPrefix.compose(intToString); + /// final result = intToPrefixedString(5); // "Number: 5" + /// ``` R Function(U) compose(final T Function(U) inner) => (final u) => this(inner(u)); } // === Apply section === + +/// Extends a function of one argument [R Function(T)] with an `apply` method. extension Apply1 on R Function(T) { + /// Applies this function to the given argument [x]. + /// + /// This method primarily offers a consistent naming pattern with other `Apply` + /// extensions for partial application, though for a single-argument function + /// it's equivalent to a direct call. + /// + /// Returns the result of `this(x)`. R apply(final T x) => this(x); } +/// Extends a function of two arguments [R Function(T, U)] with an `apply` method +/// for partial application. extension Apply2 on R Function(T, U) { + /// Partially applies the first argument [t] to this two-argument function. + /// + /// Returns a new function that takes the remaining argument [U] and, + /// when called, executes this function with [t] and the provided [U]. + /// + /// Example: + /// ```dart + /// final sum = (int a, int b) => a + b; + /// final add5 = sum.apply(5); + /// final result = add5(3); // result is 8 + /// ``` R Function(U) apply(final T t) => (final U u) => this(t, u); } +/// Extends a function of three arguments [R Function(T, U, V)] with an `apply` method +/// for partial application. extension Apply3 on R Function(T, U, V) { + /// Partially applies the first argument [t] to this three-argument function. + /// + /// Returns a new function that takes the remaining two arguments ([U], [V]) and, + /// when called, executes this function with [t] and the provided [U] and [V]. + /// + /// Example: + /// ```dart + /// final concat = (String a, String b, String c) => a + b + c; + /// final greet = concat.apply('Hello, '); + /// final result = greet('World', '!'); // "Hello, World!" + /// ``` R Function(U, V) apply(final T t) => (final U u, final V v) => this(t, u, v); } +/// Extends a function of four arguments [R Function(T, U, V, X)] with an `apply` method +/// for partial application. extension Apply4 on R Function(T, U, V, X) { + /// Partially applies the first argument [t] to this four-argument function. + /// + /// Returns a new function that takes the remaining three arguments ([U], [V], [X]) and, + /// when called, executes this function with [t] and the provided [U], [V], and [X]. + /// + /// Example: + /// ```dart + /// final sumFour = (int a, int b, int c, int d) => a + b + c + d; + /// final add1 = sumFour.apply(1); + /// final result = add1(2, 3, 4); // result is 10 + /// ``` R Function(U, V, X) apply(final T t) => (final U u, final V v, final X x) => this(t, u, v, x); } // === Pack section === + +/// Extends a function of one argument [R Function(T)] with a `pack` getter. extension Pack1 on R Function(T) { + /// A getter that returns a new function accepting a 1-element record (tuple). + /// + /// The returned function takes a single argument `(T,)` and applies this + /// original function to its element. + /// + /// Example: + /// ```dart + /// final square = (int x) => x * x; + /// final packedSquare = square.pack; + /// final result = packedSquare((5,)); // result is 25 + /// ``` R Function((T,)) get pack => (final (T,) x) => this(x.$1); } +/// Extends a function of two arguments [R Function(T, U)] with a `pack` getter. extension Pack2 on R Function(T, U) { + /// A getter that returns a new function accepting a 2-element record (tuple). + /// + /// The returned function takes a single argument `(T, U)` and applies this + /// original function to its elements. + /// + /// Example: + /// ```dart + /// final sum = (int a, int b) => a + b; + /// final packedSum = sum.pack; + /// final result = packedSum((5, 3)); // result is 8 + /// ``` R Function((T, U)) get pack => (final (T, U) x) => this(x.$1, x.$2); } +/// Extends a function of three arguments [R Function(T, U, V)] with a `pack` getter. extension Pack3 on R Function(T, U, V) { + /// A getter that returns a new function accepting a 3-element record (tuple). + /// + /// The returned function takes a single argument `(T, U, V)` and applies this + /// original function to its elements. + /// Example: + /// ```dart + /// final joinStrings = (String s1, String s2, String s3) => '$s1 $s2 $s3'; + /// final packedJoin = joinStrings.pack; + /// final result = packedJoin(('Hello', 'functional', 'world')); // "Hello functional world" + /// ``` R Function((T, U, V)) get pack => (final (T, U, V) x) => this(x.$1, x.$2, x.$3); } +/// Extends a function of four arguments [R Function(T, U, V, X)] with a `pack` getter. extension Pack4 on R Function(T, U, V, X) { + /// A getter that returns a new function accepting a 4-element record (tuple). + /// + /// The returned function takes a single argument `(T, U, V, X)` and applies this + /// original function to its elements. + /// Example: + /// ```dart + /// final sumFour = (int a, int b, int c, int d) => a + b + c + d; + /// final packedSum = sumFour.pack; + /// final result = packedSum((1,2,3,4)); // result is 10 + /// ``` R Function((T, U, V, X)) get pack => (final (T, U, V, X) x) => this(x.$1, x.$2, x.$3, x.$4); }