diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..2144819 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,8 @@ +# Dependabot configuration file. +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 430a85e..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Set update schedule for GitHub Actions -# See https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot - -version: 2 -updates: - -- package-ecosystem: "github-actions" - directory: "/" - schedule: - # Check for updates to GitHub Actions every weekday - interval: "daily" diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 7d3ad48..eeb824f 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -20,10 +20,10 @@ jobs: strategy: fail-fast: false matrix: - sdk: [2.12.0, dev] + sdk: [2.17.0, dev] steps: - - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1 + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install @@ -50,10 +50,10 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] - sdk: [2.12.0, dev] + sdk: [2.17.0, dev] steps: - - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1 + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install diff --git a/CHANGELOG.md b/CHANGELOG.md index 642f05d..0c0deb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ -# 2.0.1-dev +# 2.0.1 * Handle `expires_in` when encoded as string. * Populate the pubspec `repository` field. +* Increase the minimum Dart SDK to `2.17.0`. # 2.0.0 diff --git a/LICENSE b/LICENSE index 000cd7b..162572a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2014, the Dart project authors. +Copyright 2014, the Dart project authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/README.md b/README.md index a536740..196b9f7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ +[![Dart CI](https://github.com/dart-lang/oauth2/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/oauth2/actions/workflows/test-package.yml) +[![pub package](https://img.shields.io/pub/v/oauth2.svg)](https://pub.dev/packages/oauth2) +[![package publisher](https://img.shields.io/pub/publisher/oauth2.svg)](https://pub.dev/packages/oauth2/publisher) + A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. +## About OAuth2 + OAuth2 allows a client (the program using this library) to access and manipulate a resource that's owned by a resource owner (the end user) and lives on a remote server. The client directs the resource owner to an authorization server diff --git a/analysis_options.yaml b/analysis_options.yaml index 108d105..c8bc59c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,40 @@ -include: package:pedantic/analysis_options.yaml +include: package:lints/recommended.yaml + +analyzer: + language: + strict-casts: true + strict-raw-types: true + +linter: + rules: + - always_declare_return_types + - avoid_catching_errors + - avoid_dynamic_calls + - avoid_private_typedef_functions + - avoid_unused_constructor_parameters + - avoid_void_async + - cancel_subscriptions + - directives_ordering + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_runtimeType_toString + - omit_local_variable_types + - only_throw_errors + - package_api_docs + - prefer_asserts_in_initializer_lists + - prefer_const_constructors + - prefer_const_declarations + - prefer_relative_imports + - prefer_single_quotes + - sort_pub_dependencies + - test_types_in_equals + - throw_in_finally + - type_annotate_public_apis + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_lambdas + - unnecessary_parenthesis + - unnecessary_statements + - use_is_even_rather_than_modulo + - use_string_buffers + - use_super_parameters diff --git a/example/main.dart b/example/main.dart index 4bd3cbc..9db62fc 100644 --- a/example/main.dart +++ b/example/main.dart @@ -19,8 +19,8 @@ final tokenEndpoint = Uri.parse('http://example.com/oauth2/token'); // available may not be able to make sure the client secret is kept a // secret. This is fine; OAuth2 servers generally won't rely on knowing // with certainty that a client is who it claims to be. -final identifier = 'my client identifier'; -final secret = 'my client secret'; +const identifier = 'my client identifier'; +const secret = 'my client secret'; // This is a URL on your application's server. The authorization server // will redirect the resource owner here once they've authorized the @@ -53,8 +53,9 @@ Future createClient() async { identifier, authorizationEndpoint, tokenEndpoint, secret: secret); - // A URL on the authorization server (authorizationEndpoint with some additional - // query parameters). Scopes and state can optionally be passed into this method. + // A URL on the authorization server (authorizationEndpoint with some + // additional query parameters). Scopes and state can optionally be passed + // into this method. var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); // Redirect the resource owner to the authorization URL. Once the resource @@ -69,7 +70,7 @@ Future createClient() async { // Once the user is redirected to `redirectUrl`, pass the query parameters to // the AuthorizationCodeGrant. It will validate them and extract the // authorization code to create a new Client. - return await grant.handleAuthorizationResponse(responseUrl.queryParameters); + return grant.handleAuthorizationResponse(responseUrl.queryParameters); } void main() async { diff --git a/lib/oauth2.dart b/lib/oauth2.dart index dfd2d57..45efc5c 100644 --- a/lib/oauth2.dart +++ b/lib/oauth2.dart @@ -3,9 +3,9 @@ // BSD-style license that can be found in the LICENSE file. export 'src/authorization_code_grant.dart'; -export 'src/client_credentials_grant.dart'; -export 'src/resource_owner_password_grant.dart'; +export 'src/authorization_exception.dart'; export 'src/client.dart'; +export 'src/client_credentials_grant.dart'; export 'src/credentials.dart'; -export 'src/authorization_exception.dart'; export 'src/expiration_exception.dart'; +export 'src/resource_owner_password_grant.dart'; diff --git a/lib/src/authorization_code_grant.dart b/lib/src/authorization_code_grant.dart index fafa5f7..fac56ba 100644 --- a/lib/src/authorization_code_grant.dart +++ b/lib/src/authorization_code_grant.dart @@ -10,8 +10,8 @@ import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; -import 'client.dart'; import 'authorization_exception.dart'; +import 'client.dart'; import 'credentials.dart'; import 'handle_access_token_response.dart'; import 'parameters.dart'; @@ -107,7 +107,8 @@ class AuthorizationCodeGrant { static const String _charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; - /// The PKCE code verifier. Will be generated if one is not provided in the constructor. + /// The PKCE code verifier. Will be generated if one is not provided in the + /// constructor. final String _codeVerifier; /// Creates a new grant. @@ -126,8 +127,8 @@ class AuthorizationCodeGrant { /// [onCredentialsRefreshed] will be called by the constructed [Client] /// whenever the credentials are refreshed. /// - /// [codeVerifier] String to be used as PKCE code verifier. If none is provided a - /// random codeVerifier will be generated. + /// [codeVerifier] String to be used as PKCE code verifier. If none is + /// provided a random codeVerifier will be generated. /// The codeVerifier must meet requirements specified in [RFC 7636]. /// /// [RFC 7636]: https://tools.ietf.org/html/rfc7636#section-4.1 @@ -221,10 +222,11 @@ class AuthorizationCodeGrant { /// [getAuthorizationUrl] is called, or to call it after /// [handleAuthorizationCode] is called. /// - /// Throws [FormatException] if [parameters] is invalid according to the OAuth2 - /// spec or if the authorization server otherwise provides invalid responses. - /// If `state` was passed to [getAuthorizationUrl], this will throw a - /// [FormatException] if the `state` parameter doesn't match the original value. + /// Throws [FormatException] if [parameters] is invalid according to the + /// OAuth2 spec or if the authorization server otherwise provides invalid + /// responses. If `state` was passed to [getAuthorizationUrl], this will throw + /// a [FormatException] if the `state` parameter doesn't match the original + /// value. /// /// Throws [AuthorizationException] if the authorization fails. Future handleAuthorizationResponse( @@ -259,7 +261,7 @@ class AuthorizationCodeGrant { '"code".'); } - return await _handleAuthorizationCode(parameters['code']); + return _handleAuthorizationCode(parameters['code']); } /// Processes an authorization code directly. @@ -285,7 +287,7 @@ class AuthorizationCodeGrant { } _state = _State.finished; - return await _handleAuthorizationCode(authorizationCode); + return _handleAuthorizationCode(authorizationCode); } /// This works just like [handleAuthorizationCode], except it doesn't validate @@ -326,11 +328,12 @@ class AuthorizationCodeGrant { onCredentialsRefreshed: _onCredentialsRefreshed); } - /// Randomly generate a 128 character string to be used as the PKCE code verifier - static String _createCodeVerifier() { - return List.generate( - 128, (i) => _charset[Random.secure().nextInt(_charset.length)]).join(); - } + // Randomly generate a 128 character string to be used as the PKCE code + // verifier. + static String _createCodeVerifier() => List.generate( + 128, + (i) => _charset[Random.secure().nextInt(_charset.length)], + ).join(); /// Closes the grant and frees its resources. /// diff --git a/lib/src/credentials.dart b/lib/src/credentials.dart index 2c2ac51..459e63e 100644 --- a/lib/src/credentials.dart +++ b/lib/src/credentials.dart @@ -128,12 +128,12 @@ class Credentials { /// /// Throws a [FormatException] if the JSON is incorrectly formatted. factory Credentials.fromJson(String json) { - void validate(condition, message) { + void validate(bool condition, message) { if (condition) return; throw FormatException('Failed to load credentials: $message.\n\n$json'); } - var parsed; + dynamic parsed; try { parsed = jsonDecode(json); } on FormatException { @@ -141,12 +141,15 @@ class Credentials { } validate(parsed is Map, 'was not a JSON map'); + + parsed = parsed as Map; validate(parsed.containsKey('accessToken'), 'did not contain required field "accessToken"'); validate( - parsed['accessToken'] is String, - 'required field "accessToken" was not a string, was ' - '${parsed["accessToken"]}'); + parsed['accessToken'] is String, + 'required field "accessToken" was not a string, was ' + '${parsed["accessToken"]}', + ); for (var stringField in ['refreshToken', 'idToken', 'tokenEndpoint']) { var value = parsed[stringField]; @@ -159,22 +162,28 @@ class Credentials { 'field "scopes" was not a list, was "$scopes"'); var tokenEndpoint = parsed['tokenEndpoint']; + Uri? tokenEndpointUri; if (tokenEndpoint != null) { - tokenEndpoint = Uri.parse(tokenEndpoint); + tokenEndpointUri = Uri.parse(tokenEndpoint as String); } + var expiration = parsed['expiration']; + DateTime? expirationDateTime; if (expiration != null) { validate(expiration is int, 'field "expiration" was not an int, was "$expiration"'); - expiration = DateTime.fromMillisecondsSinceEpoch(expiration); + expiration = expiration as int; + expirationDateTime = DateTime.fromMillisecondsSinceEpoch(expiration); } - return Credentials(parsed['accessToken'], - refreshToken: parsed['refreshToken'], - idToken: parsed['idToken'], - tokenEndpoint: tokenEndpoint, - scopes: (scopes as List).map((scope) => scope as String), - expiration: expiration); + return Credentials( + parsed['accessToken'] as String, + refreshToken: parsed['refreshToken'] as String?, + idToken: parsed['idToken'] as String?, + tokenEndpoint: tokenEndpointUri, + scopes: (scopes as List).map((scope) => scope as String), + expiration: expirationDateTime, + ); } /// Serializes a set of credentials to JSON. diff --git a/lib/src/handle_access_token_response.dart b/lib/src/handle_access_token_response.dart index 4c92810..931ae9d 100644 --- a/lib/src/handle_access_token_response.dart +++ b/lib/src/handle_access_token_response.dart @@ -5,8 +5,8 @@ import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; -import 'credentials.dart'; import 'authorization_exception.dart'; +import 'credentials.dart'; import 'parameters.dart'; /// The amount of time to add as a "grace period" for credential expiration. @@ -43,7 +43,7 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, var contentTypeString = response.headers['content-type']; if (contentTypeString == null) { - throw FormatException('Missing Content-Type string.'); + throw const FormatException('Missing Content-Type string.'); } var parameters = @@ -62,7 +62,7 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, // TODO(nweiz): support the "mac" token type // (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01) - if (parameters['token_type'].toLowerCase() != 'bearer') { + if ((parameters['token_type'] as String).toLowerCase() != 'bearer') { throw FormatException( '"$tokenEndpoint": unknown token type "${parameters['token_type']}"'); } @@ -95,14 +95,16 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, var expiration = expiresIn == null ? null - : startTime.add(Duration(seconds: expiresIn) - _expirationGrace); - - return Credentials(parameters['access_token'], - refreshToken: parameters['refresh_token'], - idToken: parameters['id_token'], - tokenEndpoint: tokenEndpoint, - scopes: scopes, - expiration: expiration); + : startTime.add(Duration(seconds: expiresIn as int) - _expirationGrace); + + return Credentials( + parameters['access_token'] as String, + refreshToken: parameters['refresh_token'] as String?, + idToken: parameters['id_token'] as String?, + tokenEndpoint: tokenEndpoint, + scopes: scopes, + expiration: expiration, + ); } on FormatException catch (e) { throw FormatException('Invalid OAuth response for "$tokenEndpoint": ' '${e.message}.\n\n${response.body}'); @@ -133,7 +135,7 @@ void _handleErrorResponse( var parameters = getParameters(contentType, response.body); if (!parameters.containsKey('error')) { - throw FormatException('did not contain required parameter "error"'); + throw const FormatException('did not contain required parameter "error"'); } else if (parameters['error'] is! String) { throw FormatException('required parameter "error" was not a string, was ' '"${parameters["error"]}"'); @@ -147,8 +149,8 @@ void _handleErrorResponse( } } - var description = parameters['error_description']; - var uriString = parameters['error_uri']; + var uriString = parameters['error_uri'] as String?; var uri = uriString == null ? null : Uri.parse(uriString); - throw AuthorizationException(parameters['error'], description, uri); + var description = parameters['error_description'] as String?; + throw AuthorizationException(parameters['error'] as String, description, uri); } diff --git a/lib/src/parameters.dart b/lib/src/parameters.dart index 789f056..ecc6559 100644 --- a/lib/src/parameters.dart +++ b/lib/src/parameters.dart @@ -10,8 +10,8 @@ import 'package:http_parser/http_parser.dart'; typedef GetParameters = Map Function( MediaType? contentType, String body); -/// Parses parameters from a response with a JSON body, as per the [OAuth2 -/// spec][]. +/// Parses parameters from a response with a JSON body, as per the +/// [OAuth2 spec][]. /// /// [OAuth2 spec]: https://tools.ietf.org/html/rfc6749#section-5.1 Map parseJsonParameters(MediaType? contentType, String body) { diff --git a/lib/src/resource_owner_password_grant.dart b/lib/src/resource_owner_password_grant.dart index b1cc772..96fb503 100644 --- a/lib/src/resource_owner_password_grant.dart +++ b/lib/src/resource_owner_password_grant.dart @@ -8,9 +8,9 @@ import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; import 'client.dart'; +import 'credentials.dart'; import 'handle_access_token_response.dart'; import 'utils.dart'; -import 'credentials.dart'; /// Obtains credentials using a [resource owner password grant](https://tools.ietf.org/html/rfc6749#section-1.3.3). /// diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 50a925a..2a22b9f 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -10,6 +10,6 @@ Uri addQueryParameters(Uri url, Map parameters) => url.replace( queryParameters: Map.from(url.queryParameters)..addAll(parameters)); String basicAuthHeader(String identifier, String secret) { - var userPass = Uri.encodeFull(identifier) + ':' + Uri.encodeFull(secret); - return 'Basic ' + base64Encode(ascii.encode(userPass)); + var userPass = '${Uri.encodeFull(identifier)}:${Uri.encodeFull(secret)}'; + return 'Basic ${base64Encode(ascii.encode(userPass))}'; } diff --git a/pubspec.yaml b/pubspec.yaml index a07f648..bb43bb7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 2.0.1-dev +version: 2.0.1 description: >- A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's @@ -7,7 +7,7 @@ description: >- repository: https://github.com/dart-lang/oauth2 environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.17.0 <3.0.0' dependencies: collection: ^1.15.0 @@ -16,5 +16,5 @@ dependencies: http_parser: ^4.0.0 dev_dependencies: - pedantic: ^1.10.0 + lints: ^2.0.0 test: ^1.16.0 diff --git a/test/authorization_code_grant_test.dart b/test/authorization_code_grant_test.dart index 8010bcf..b4807bc 100644 --- a/test/authorization_code_grant_test.dart +++ b/test/authorization_code_grant_test.dart @@ -5,9 +5,9 @@ import 'dart:async'; import 'dart:convert'; -import 'package:test/test.dart'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:test/test.dart'; import 'utils.dart'; @@ -55,9 +55,10 @@ void main() { }); test('builds the correct URL with passed in code verifier', () { - final codeVerifier = - 'it1shei7LooGoh3looxaa4sieveijeib2zecauz2oo8aebae5aehee0ahPirewoh5Bo6Maexooqui3uL2si6ahweiv7shauc1shahxooveoB3aeyahsaiye0Egh3raix'; - final expectedCodeChallenge = + const codeVerifier = + 'it1shei7LooGoh3looxaa4sieveijeib2zecauz2oo8aebae5aehee0ahPirewoh' + '5Bo6Maexooqui3uL2si6ahweiv7shauc1shahxooveoB3aeyahsaiye0Egh3raix'; + const expectedCodeChallenge = 'EjfFMv8TFPd3GuNxAn5COhlWBGpfZLimHett7ypJfJ0'; var grant = oauth2.AuthorizationCodeGrant( 'identifier', @@ -349,8 +350,9 @@ void main() { }); grant.getAuthorizationUrl(redirectUrl); - client.expectRequest((request) { - return Future.value(http.Response( + client.expectRequest( + (request) => Future.value( + http.Response( jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', @@ -358,22 +360,28 @@ void main() { 'refresh_token': 'refresh token', }), 200, - headers: {'content-type': 'application/json'})); - }); + headers: {'content-type': 'application/json'}, + ), + ), + ); var oauth2Client = await grant.handleAuthorizationCode('auth code'); - client.expectRequest((request) { - return Future.value(http.Response( - jsonEncode( - {'access_token': 'new access token', 'token_type': 'bearer'}), + client.expectRequest( + (request) => Future.value( + http.Response( + jsonEncode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + }), 200, - headers: {'content-type': 'application/json'})); - }); + headers: {'content-type': 'application/json'}, + ), + ), + ); - client.expectRequest((request) { - return Future.value(http.Response('good job', 200)); - }); + client.expectRequest( + (request) => Future.value(http.Response('good job', 200))); await oauth2Client.read(Uri.parse('http://example.com/resource')); diff --git a/test/client_credentials_grant_test.dart b/test/client_credentials_grant_test.dart index 9e27c9e..67c71a5 100644 --- a/test/client_credentials_grant_test.dart +++ b/test/client_credentials_grant_test.dart @@ -19,11 +19,12 @@ final success = jsonEncode({ 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', }); -var auth = 'Basic Y2xpZW50OnNlY3JldA=='; -var authEndpoint = Uri.parse('https://example.com'); +String auth = 'Basic Y2xpZW50OnNlY3JldA=='; +Uri authEndpoint = Uri.parse('https://example.com'); void main() { - var expectClient; + late ExpectClient expectClient; + setUp(() => expectClient = ExpectClient()); group('basic', () { diff --git a/test/client_test.dart b/test/client_test.dart index 5960cbd..b583f8c 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -16,12 +16,13 @@ final Uri requestUri = Uri.parse('http://example.com/resource'); final Uri tokenEndpoint = Uri.parse('http://example.com/token'); void main() { - var httpClient; + late ExpectClient httpClient; + setUp(() => httpClient = ExpectClient()); group('with expired credentials', () { test("that can't be refreshed throws an ExpirationException on send", () { - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', expiration: expiration); var client = oauth2.Client(credentials, @@ -34,7 +35,7 @@ void main() { test( 'that can be refreshed refreshes the credentials and sends the ' 'request', () async { - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, @@ -68,7 +69,7 @@ void main() { test( 'that can be refreshed refreshes only once if multiple requests are made', () async { - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, @@ -86,7 +87,7 @@ void main() { headers: {'content-type': 'application/json'})); }); - final numCalls = 2; + const numCalls = 2; for (var i = 0; i < numCalls; i++) { httpClient.expectRequest((request) { @@ -100,7 +101,8 @@ void main() { } await Future.wait( - List.generate(numCalls, (_) => client.read(requestUri))); + List>.generate(numCalls, (_) => client.read(requestUri)), + ); expect(client.credentials.accessToken, equals('new access token')); }); @@ -108,7 +110,7 @@ void main() { test('that onCredentialsRefreshed is called', () async { var callbackCalled = false; - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, @@ -121,17 +123,20 @@ void main() { expect(credentials.accessToken, equals('new access token')); }); - httpClient.expectRequest((request) { - return Future.value(http.Response( + httpClient.expectRequest( + (request) => Future.value( + http.Response( jsonEncode( - {'access_token': 'new access token', 'token_type': 'bearer'}), + {'access_token': 'new access token', 'token_type': 'bearer'}, + ), 200, - headers: {'content-type': 'application/json'})); - }); + headers: {'content-type': 'application/json'}, + ), + ), + ); - httpClient.expectRequest((request) { - return Future.value(http.Response('good job', 200)); - }); + httpClient.expectRequest( + (request) => Future.value(http.Response('good job', 200))); await client.read(requestUri); expect(callbackCalled, equals(true)); diff --git a/test/credentials_test.dart b/test/credentials_test.dart index 1b3caa5..d83bc7e 100644 --- a/test/credentials_test.dart +++ b/test/credentials_test.dart @@ -14,7 +14,8 @@ import 'utils.dart'; final Uri tokenEndpoint = Uri.parse('http://example.com/token'); void main() { - var httpClient; + late ExpectClient httpClient; + setUp(() => httpClient = ExpectClient()); test('is not expired if no expiration exists', () { @@ -23,14 +24,14 @@ void main() { }); test('is not expired if the expiration is in the future', () { - var expiration = DateTime.now().add(Duration(hours: 1)); + var expiration = DateTime.now().add(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', expiration: expiration); expect(credentials.isExpired, isFalse); }); test('is expired if the expiration is in the past', () { - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', expiration: expiration); expect(credentials.isExpired, isTrue); @@ -257,14 +258,14 @@ void main() { }); group('fromJson', () { - oauth2.Credentials fromMap(Map map) => + oauth2.Credentials fromMap(Map map) => oauth2.Credentials.fromJson(jsonEncode(map)); test('should load the same credentials from toJson', () { // Round the expiration down to milliseconds since epoch, since that's // what the credentials file stores. Otherwise sub-millisecond time gets // in the way. - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); expiration = DateTime.fromMillisecondsSinceEpoch( expiration.millisecondsSinceEpoch); diff --git a/test/handle_access_token_response_test.dart b/test/handle_access_token_response_test.dart index 1663d3f..481c1af 100644 --- a/test/handle_access_token_response_test.dart +++ b/test/handle_access_token_response_test.dart @@ -5,11 +5,10 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:test/test.dart'; - import 'package:oauth2/oauth2.dart' as oauth2; import 'package:oauth2/src/handle_access_token_response.dart'; import 'package:oauth2/src/parameters.dart'; +import 'package:test/test.dart'; import 'utils.dart'; @@ -34,7 +33,7 @@ void main() { handle(http.Response(body, statusCode, headers: headers)); test('causes an AuthorizationException', () { - expect(() => handleError(), throwsAuthorizationException); + expect(handleError, throwsAuthorizationException); }); test('with a 401 code causes an AuthorizationException', () { @@ -163,11 +162,15 @@ void main() { }); test('with custom getParameters() returns the correct credentials', () { - var body = '_' + - jsonEncode({'token_type': 'bearer', 'access_token': 'access token'}); + var body = '_${jsonEncode({ + 'token_type': 'bearer', + 'access_token': 'access token' + })}'; var credentials = handle( - http.Response(body, 200, headers: {'content-type': 'text/plain'}), - getParameters: (contentType, body) => jsonDecode(body.substring(1))); + http.Response(body, 200, headers: {'content-type': 'text/plain'}), + getParameters: (contentType, body) => + jsonDecode(body.substring(1)) as Map, + ); expect(credentials.accessToken, equals('access token')); expect(credentials.tokenEndpoint.toString(), equals(tokenEndpoint.toString())); diff --git a/test/resource_owner_password_grant_test.dart b/test/resource_owner_password_grant_test.dart index c9afb65..2614b14 100644 --- a/test/resource_owner_password_grant_test.dart +++ b/test/resource_owner_password_grant_test.dart @@ -3,8 +3,8 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('vm') -import 'dart:convert'; import 'dart:async'; +import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; @@ -19,11 +19,12 @@ final success = jsonEncode({ 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', }); -var auth = 'Basic Y2xpZW50OnNlY3JldA=='; -var authEndpoint = Uri.parse('https://example.com'); +String auth = 'Basic Y2xpZW50OnNlY3JldA=='; +Uri authEndpoint = Uri.parse('https://example.com'); void main() { - var expectClient; + late ExpectClient expectClient; + setUp(() => expectClient = ExpectClient()); group('basic', () { diff --git a/test/utils.dart b/test/utils.dart index f66e7df..90980c8 100644 --- a/test/utils.dart +++ b/test/utils.dart @@ -13,9 +13,7 @@ import 'package:test/test.dart'; class ExpectClient extends MockClient { final Queue _handlers; - ExpectClient._(MockClientHandler fn) - : _handlers = Queue(), - super(fn); + ExpectClient._(super.fn) : _handlers = Queue(); factory ExpectClient() { late ExpectClient client;