Migrate to null safety (#102)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bb8c5a..dd402ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,11 @@
+# 2.0.0-dev
+
+* Migrate to null safety.
+
# 1.6.3
* Added optional `codeVerifier` parameter to `AuthorizationCodeGrant` constructor.
-# 1.6.2-dev
-
# 1.6.1
* Added fix to make sure that credentials are only refreshed once when multiple calls are made.
diff --git a/example/main.dart b/example/main.dart
index 1560cdd..4bd3cbc 100644
--- a/example/main.dart
+++ b/example/main.dart
@@ -76,7 +76,7 @@
var client = await createClient();
// Once you have a Client, you can use it just like any other HTTP client.
- print(await client.read('http://example.com/protected-resources.txt'));
+ print(await client.read(Uri.http('example.com', 'protected-resources.txt')));
// Once we're done with the client, save the credentials file. This ensures
// that if the credentials were automatically refreshed while using the
@@ -91,5 +91,5 @@
Future<Uri> listen(Uri url) async {
// Client implementation detail
- return null;
+ return Uri();
}
diff --git a/lib/src/authorization_code_grant.dart b/lib/src/authorization_code_grant.dart
index 963e5b2..4a7ef8a 100644
--- a/lib/src/authorization_code_grant.dart
+++ b/lib/src/authorization_code_grant.dart
@@ -58,7 +58,7 @@
/// 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 String secret;
+ final String? secret;
/// A URL provided by the authorization server that serves as the base for the
/// URL that the resource owner will be redirected to to authorize this
@@ -78,7 +78,7 @@
/// Callback to be invoked whenever the credentials are refreshed.
///
/// This will be passed as-is to the constructed [Client].
- final CredentialsRefreshedCallback _onCredentialsRefreshed;
+ final CredentialsRefreshedCallback? _onCredentialsRefreshed;
/// Whether to use HTTP Basic authentication for authorizing the client.
final bool _basicAuth;
@@ -87,18 +87,18 @@
final String _delimiter;
/// The HTTP client used to make HTTP requests.
- http.Client _httpClient;
+ http.Client? _httpClient;
/// The URL to which the resource owner will be redirected after they
/// authorize this client with the authorization server.
- Uri _redirectEndpoint;
+ Uri? _redirectEndpoint;
/// The scopes that the client is requesting access to.
- List<String> _scopes;
+ List<String>? _scopes;
/// An opaque string that users of this library may specify that will be
/// included in the response query parameters.
- String _stateString;
+ String? _stateString;
/// The current state of the grant object.
_State _state = _State.initial;
@@ -148,13 +148,13 @@
AuthorizationCodeGrant(
this.identifier, this.authorizationEndpoint, this.tokenEndpoint,
{this.secret,
- String delimiter,
+ String? delimiter,
bool basicAuth = true,
- http.Client httpClient,
- CredentialsRefreshedCallback onCredentialsRefreshed,
- Map<String, dynamic> Function(MediaType contentType, String body)
+ http.Client? httpClient,
+ CredentialsRefreshedCallback? onCredentialsRefreshed,
+ Map<String, dynamic> Function(MediaType? contentType, String body)?
getParameters,
- String codeVerifier})
+ String? codeVerifier})
: _basicAuth = basicAuth,
_httpClient = httpClient ?? http.Client(),
_delimiter = delimiter ?? ' ',
@@ -182,24 +182,19 @@
///
/// It is a [StateError] to call this more than once.
Uri getAuthorizationUrl(Uri redirect,
- {Iterable<String> scopes, String state}) {
+ {Iterable<String>? scopes, String? state}) {
if (_state != _State.initial) {
throw StateError('The authorization URL has already been generated.');
}
_state = _State.awaitingResponse;
- if (scopes == null) {
- scopes = [];
- } else {
- scopes = scopes.toList();
- }
-
+ var scopeList = scopes?.toList() ?? <String>[];
var codeChallenge = base64Url
.encode(sha256.convert(ascii.encode(_codeVerifier)).bytes)
.replaceAll('=', '');
_redirectEndpoint = redirect;
- _scopes = scopes;
+ _scopes = scopeList;
_stateString = state;
var parameters = {
'response_type': 'code',
@@ -210,7 +205,7 @@
};
if (state != null) parameters['state'] = state;
- if (scopes.isNotEmpty) parameters['scope'] = scopes.join(_delimiter);
+ if (scopeList.isNotEmpty) parameters['scope'] = scopeList.join(_delimiter);
return addQueryParameters(authorizationEndpoint, parameters);
}
@@ -257,7 +252,7 @@
var description = parameters['error_description'];
var uriString = parameters['error_uri'];
var uri = uriString == null ? null : Uri.parse(uriString);
- throw AuthorizationException(parameters['error'], description, uri);
+ throw AuthorizationException(parameters['error']!, description, uri);
} else if (!parameters.containsKey('code')) {
throw FormatException('Invalid OAuth response for '
'"$authorizationEndpoint": did not contain required parameter '
@@ -295,7 +290,7 @@
/// This works just like [handleAuthorizationCode], except it doesn't validate
/// the state beforehand.
- Future<Client> _handleAuthorizationCode(String authorizationCode) async {
+ Future<Client> _handleAuthorizationCode(String? authorizationCode) async {
var startTime = DateTime.now();
var headers = <String, String>{};
@@ -307,6 +302,7 @@
'code_verifier': _codeVerifier
};
+ var secret = this.secret;
if (_basicAuth && secret != null) {
headers['Authorization'] = basicAuthHeader(identifier, secret);
} else {
@@ -317,7 +313,7 @@
}
var response =
- await _httpClient.post(tokenEndpoint, headers: headers, body: body);
+ await _httpClient!.post(tokenEndpoint, headers: headers, body: body);
var credentials = handleAccessTokenResponse(
response, tokenEndpoint, startTime, _scopes, _delimiter,
@@ -342,7 +338,7 @@
/// [Client] created by this grant, so it's not safe to close the grant and
/// continue using the client.
void close() {
- if (_httpClient != null) _httpClient.close();
+ _httpClient?.close();
_httpClient = null;
}
}
diff --git a/lib/src/authorization_exception.dart b/lib/src/authorization_exception.dart
index a1b6dd9..14a5a3c 100644
--- a/lib/src/authorization_exception.dart
+++ b/lib/src/authorization_exception.dart
@@ -14,13 +14,13 @@
/// The description of the error, provided by the server.
///
/// May be `null` if the server provided no description.
- final String description;
+ final String? description;
/// A URL for a page that describes the error in more detail, provided by the
/// server.
///
/// May be `null` if the server provided no URL.
- final Uri uri;
+ final Uri? uri;
/// Creates an AuthorizationException.
AuthorizationException(this.error, this.description, this.uri);
diff --git a/lib/src/client.dart b/lib/src/client.dart
index 7a78aa3..dc50e43 100644
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -4,6 +4,7 @@
import 'dart:async';
+import 'package:collection/collection.dart';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
@@ -40,7 +41,7 @@
/// pair that any client may use.
///
/// This is usually global to the program using this library.
- final String identifier;
+ final String? identifier;
/// The client secret for this client.
///
@@ -55,7 +56,7 @@
/// 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 String secret;
+ final String? secret;
/// The credentials this client uses to prove to the resource server that it's
/// authorized.
@@ -66,13 +67,13 @@
Credentials _credentials;
/// Callback to be invoked whenever the credentials refreshed.
- final CredentialsRefreshedCallback _onCredentialsRefreshed;
+ final CredentialsRefreshedCallback? _onCredentialsRefreshed;
/// Whether to use HTTP Basic authentication for authorizing the client.
final bool _basicAuth;
/// The underlying HTTP client.
- http.Client _httpClient;
+ http.Client? _httpClient;
/// Creates a new client from a pre-existing set of credentials.
///
@@ -87,9 +88,9 @@
Client(this._credentials,
{this.identifier,
this.secret,
- CredentialsRefreshedCallback onCredentialsRefreshed,
+ CredentialsRefreshedCallback? onCredentialsRefreshed,
bool basicAuth = true,
- http.Client httpClient})
+ http.Client? httpClient})
: _basicAuth = basicAuth,
_onCredentialsRefreshed = onCredentialsRefreshed,
_httpClient = httpClient ?? http.Client() {
@@ -110,33 +111,32 @@
}
request.headers['authorization'] = 'Bearer ${credentials.accessToken}';
- var response = await _httpClient.send(request);
+ var response = await _httpClient!.send(request);
if (response.statusCode != 401) return response;
if (!response.headers.containsKey('www-authenticate')) return response;
- var challenges;
+ List<AuthenticationChallenge> challenges;
try {
challenges = AuthenticationChallenge.parseHeader(
- response.headers['www-authenticate']);
+ response.headers['www-authenticate']!);
} on FormatException {
return response;
}
- var challenge = challenges.firstWhere(
- (challenge) => challenge.scheme == 'bearer',
- orElse: () => null);
+ var challenge = challenges
+ .firstWhereOrNull((challenge) => challenge.scheme == 'bearer');
if (challenge == null) return response;
var params = challenge.parameters;
if (!params.containsKey('error')) return response;
- throw AuthorizationException(params['error'], params['error_description'],
- params['error_uri'] == null ? null : Uri.parse(params['error_uri']));
+ throw AuthorizationException(params['error']!, params['error_description'],
+ params['error_uri'] == null ? null : Uri.parse(params['error_uri']!));
}
/// A [Future] used to track whether [refreshCredentials] is running.
- Future<Credentials> _refreshingFuture;
+ Future<Credentials>? _refreshingFuture;
/// Explicitly refreshes this client's credentials. Returns this client.
///
@@ -147,7 +147,7 @@
/// You may request different scopes than the default by passing in
/// [newScopes]. These must be a subset of the scopes in the
/// [Credentials.scopes] field of [Client.credentials].
- Future<Client> refreshCredentials([List<String> newScopes]) async {
+ Future<Client> refreshCredentials([List<String>? newScopes]) async {
if (!credentials.canRefresh) {
var prefix = 'OAuth credentials';
if (credentials.isExpired) prefix = '$prefix have expired and';
@@ -166,7 +166,7 @@
basicAuth: _basicAuth,
httpClient: _httpClient,
);
- _credentials = await _refreshingFuture;
+ _credentials = await _refreshingFuture!;
_onCredentialsRefreshed?.call(_credentials);
} finally {
_refreshingFuture = null;
@@ -181,7 +181,7 @@
/// Closes this client and its underlying HTTP client.
@override
void close() {
- if (_httpClient != null) _httpClient.close();
+ _httpClient?.close();
_httpClient = null;
}
}
diff --git a/lib/src/client_credentials_grant.dart b/lib/src/client_credentials_grant.dart
index c6d34b9..ab13a3f 100644
--- a/lib/src/client_credentials_grant.dart
+++ b/lib/src/client_credentials_grant.dart
@@ -40,12 +40,12 @@
/// its body as a UTF-8-decoded string. It should return a map in the same
/// format as the [standard JSON response](https://tools.ietf.org/html/rfc6749#section-5.1)
Future<Client> clientCredentialsGrant(
- Uri authorizationEndpoint, String identifier, String secret,
- {Iterable<String> scopes,
+ Uri authorizationEndpoint, String? identifier, String? secret,
+ {Iterable<String>? scopes,
bool basicAuth = true,
- http.Client httpClient,
- String delimiter,
- Map<String, dynamic> Function(MediaType contentType, String body)
+ http.Client? httpClient,
+ String? delimiter,
+ Map<String, dynamic> Function(MediaType? contentType, String body)?
getParameters}) async {
delimiter ??= ' ';
var startTime = DateTime.now();
@@ -56,7 +56,7 @@
if (identifier != null) {
if (basicAuth) {
- headers['Authorization'] = basicAuthHeader(identifier, secret);
+ headers['Authorization'] = basicAuthHeader(identifier, secret!);
} else {
body['client_id'] = identifier;
if (secret != null) body['client_secret'] = secret;
@@ -71,8 +71,8 @@
var response = await httpClient.post(authorizationEndpoint,
headers: headers, body: body);
- var credentials = await handleAccessTokenResponse(
- response, authorizationEndpoint, startTime, scopes, delimiter,
+ var credentials = await handleAccessTokenResponse(response,
+ authorizationEndpoint, startTime, scopes?.toList() ?? [], delimiter,
getParameters: getParameters);
return Client(credentials,
identifier: identifier, secret: secret, httpClient: httpClient);
diff --git a/lib/src/credentials.dart b/lib/src/credentials.dart
index 2b40b05..88a8f45 100644
--- a/lib/src/credentials.dart
+++ b/lib/src/credentials.dart
@@ -42,7 +42,7 @@
/// credentials.
///
/// This may be `null`, indicating that the credentials can't be refreshed.
- final String refreshToken;
+ final String? refreshToken;
/// The token that is received from the authorization server to enable
/// End-Users to be Authenticated, contains Claims, represented as a
@@ -52,25 +52,25 @@
/// requested (or not supported).
///
/// [spec]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken
- final String idToken;
+ final String? idToken;
/// The URL of the authorization server endpoint that's used to refresh the
/// credentials.
///
/// This may be `null`, indicating that the credentials can't be refreshed.
- final Uri tokenEndpoint;
+ final Uri? tokenEndpoint;
/// The specific permissions being requested from the authorization server.
///
/// The scope strings are specific to the authorization server and may be
/// found in its documentation.
- final List<String> scopes;
+ final List<String>? scopes;
/// The date at which these credentials will expire.
///
/// This is likely to be a few seconds earlier than the server's idea of the
/// expiration date.
- final DateTime expiration;
+ final DateTime? expiration;
/// The function used to parse parameters from a host's response.
final GetParameters _getParameters;
@@ -80,8 +80,10 @@
/// Note that it's possible the credentials will expire shortly after this is
/// called. However, since the client's expiration date is kept a few seconds
/// earlier than the server's, there should be enough leeway to rely on this.
- bool get isExpired =>
- expiration != null && DateTime.now().isAfter(expiration);
+ bool get isExpired {
+ var expiration = this.expiration;
+ return expiration != null && DateTime.now().isAfter(expiration);
+ }
/// Whether it's possible to refresh these credentials.
bool get canRefresh => refreshToken != null && tokenEndpoint != null;
@@ -110,10 +112,10 @@
{this.refreshToken,
this.idToken,
this.tokenEndpoint,
- Iterable<String> scopes,
+ Iterable<String>? scopes,
this.expiration,
- String delimiter,
- Map<String, dynamic> Function(MediaType mediaType, String body)
+ String? delimiter,
+ Map<String, dynamic> Function(MediaType? mediaType, String body)?
getParameters})
: scopes = UnmodifiableListView(
// Explicitly type-annotate the list literal to work around
@@ -183,11 +185,9 @@
'accessToken': accessToken,
'refreshToken': refreshToken,
'idToken': idToken,
- 'tokenEndpoint':
- tokenEndpoint == null ? null : tokenEndpoint.toString(),
+ 'tokenEndpoint': tokenEndpoint?.toString(),
'scopes': scopes,
- 'expiration':
- expiration == null ? null : expiration.millisecondsSinceEpoch
+ 'expiration': expiration?.millisecondsSinceEpoch
});
/// Returns a new set of refreshed credentials.
@@ -203,11 +203,11 @@
/// [AuthorizationException] if refreshing the credentials fails, or a
/// [FormatError] if the authorization server returns invalid responses.
Future<Credentials> refresh(
- {String identifier,
- String secret,
- Iterable<String> newScopes,
+ {String? identifier,
+ String? secret,
+ Iterable<String>? newScopes,
bool basicAuth = true,
- http.Client httpClient}) async {
+ http.Client? httpClient}) async {
var scopes = this.scopes;
if (newScopes != null) scopes = newScopes.toList();
scopes ??= [];
@@ -218,6 +218,7 @@
}
var startTime = DateTime.now();
+ var tokenEndpoint = this.tokenEndpoint;
if (refreshToken == null) {
throw StateError("Can't refresh credentials without a refresh "
'token.');
@@ -232,7 +233,7 @@
if (scopes.isNotEmpty) body['scope'] = scopes.join(_delimiter);
if (basicAuth && secret != null) {
- headers['Authorization'] = basicAuthHeader(identifier, secret);
+ headers['Authorization'] = basicAuthHeader(identifier!, secret);
} else {
if (identifier != null) body['client_id'] = identifier;
if (secret != null) body['client_secret'] = secret;
diff --git a/lib/src/handle_access_token_response.dart b/lib/src/handle_access_token_response.dart
index 7517af1..370ffcb 100644
--- a/lib/src/handle_access_token_response.dart
+++ b/lib/src/handle_access_token_response.dart
@@ -31,8 +31,8 @@
///
/// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1
Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint,
- DateTime startTime, List<String> scopes, String delimiter,
- {Map<String, dynamic> Function(MediaType contentType, String body)
+ DateTime startTime, List<String>? scopes, String delimiter,
+ {Map<String, dynamic> Function(MediaType? contentType, String body)?
getParameters}) {
getParameters ??= parseJsonParameters;
@@ -81,7 +81,7 @@
}
}
- var scope = parameters['scope'] as String;
+ var scope = parameters['scope'] as String?;
if (scope != null) scopes = scope.split(delimiter);
var expiration = expiresIn == null
@@ -109,8 +109,9 @@
// off-spec.
if (response.statusCode != 400 && response.statusCode != 401) {
var reason = '';
- if (response.reasonPhrase != null && response.reasonPhrase.isNotEmpty) {
- ' ${response.reasonPhrase}';
+ var reasonPhrase = response.reasonPhrase;
+ if (reasonPhrase != null && reasonPhrase.isNotEmpty) {
+ reason = ' $reasonPhrase';
}
throw FormatException('OAuth request for "$tokenEndpoint" failed '
'with status ${response.statusCode}$reason.\n\n${response.body}');
diff --git a/lib/src/parameters.dart b/lib/src/parameters.dart
index e8290fc..789f056 100644
--- a/lib/src/parameters.dart
+++ b/lib/src/parameters.dart
@@ -8,13 +8,13 @@
/// The type of a callback that parses parameters from an HTTP response.
typedef GetParameters = Map<String, dynamic> Function(
- MediaType contentType, String body);
+ MediaType? contentType, String body);
/// 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<String, dynamic> parseJsonParameters(MediaType contentType, String body) {
+Map<String, dynamic> parseJsonParameters(MediaType? contentType, String body) {
// The spec requires a content-type of application/json, but some endpoints
// (e.g. Dropbox) serve it as text/javascript instead.
if (contentType == null ||
diff --git a/lib/src/resource_owner_password_grant.dart b/lib/src/resource_owner_password_grant.dart
index 118f7c9..f54ac85 100644
--- a/lib/src/resource_owner_password_grant.dart
+++ b/lib/src/resource_owner_password_grant.dart
@@ -46,14 +46,14 @@
/// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1
Future<Client> resourceOwnerPasswordGrant(
Uri authorizationEndpoint, String username, String password,
- {String identifier,
- String secret,
- Iterable<String> scopes,
+ {String? identifier,
+ String? secret,
+ Iterable<String>? scopes,
bool basicAuth = true,
- CredentialsRefreshedCallback onCredentialsRefreshed,
- http.Client httpClient,
- String delimiter,
- Map<String, dynamic> Function(MediaType contentType, String body)
+ CredentialsRefreshedCallback? onCredentialsRefreshed,
+ http.Client? httpClient,
+ String? delimiter,
+ Map<String, dynamic> Function(MediaType? contentType, String body)?
getParameters}) async {
delimiter ??= ' ';
var startTime = DateTime.now();
@@ -68,7 +68,7 @@
if (identifier != null) {
if (basicAuth) {
- headers['Authorization'] = basicAuthHeader(identifier, secret);
+ headers['Authorization'] = basicAuthHeader(identifier, secret!);
} else {
body['client_id'] = identifier;
if (secret != null) body['client_secret'] = secret;
@@ -84,7 +84,7 @@
headers: headers, body: body);
var credentials = await handleAccessTokenResponse(
- response, authorizationEndpoint, startTime, scopes, delimiter,
+ response, authorizationEndpoint, startTime, scopes?.toList(), delimiter,
getParameters: getParameters);
return Client(credentials,
identifier: identifier,
diff --git a/pubspec.yaml b/pubspec.yaml
index 3fad972..ab42be2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: oauth2
-version: 1.6.3
+version: 2.0.0-dev
homepage: https://github.com/dart-lang/oauth2
description: >-
A client library for authenticating with a remote service via OAuth2 on
@@ -7,13 +7,14 @@
OAuth2 credentials.
environment:
- sdk: '>=2.0.0 <3.0.0'
+ sdk: '>=2.12.0-0 <3.0.0'
dependencies:
- http: '>=0.11.0 <0.13.0'
- http_parser: '>=1.0.0 <4.0.0'
- crypto: '^2.1.3'
+ collection: ^1.15.0
+ crypto: ^3.0.0
+ http: ^0.13.0
+ http_parser: ^4.0.0
dev_dependencies:
- pedantic: ^1.2.0
- test: ^1.0.0
+ pedantic: ^1.10.0
+ test: ^1.16.0
diff --git a/test/authorization_code_grant_test.dart b/test/authorization_code_grant_test.dart
index f74c061..8010bcf 100644
--- a/test/authorization_code_grant_test.dart
+++ b/test/authorization_code_grant_test.dart
@@ -14,8 +14,8 @@
final redirectUrl = Uri.parse('http://example.com/redirect');
void main() {
- ExpectClient client;
- oauth2.AuthorizationCodeGrant grant;
+ late ExpectClient client;
+ late oauth2.AuthorizationCodeGrant grant;
setUp(() {
client = ExpectClient();
grant = oauth2.AuthorizationCodeGrant(
@@ -223,7 +223,7 @@
expect(grant.handleAuthorizationCode('auth code'), throwsStateError);
});
- test('sends an authorization code request', () {
+ test('sends an authorization code request', () async {
grant.getAuthorizationUrl(redirectUrl);
client.expectRequest((request) {
expect(request.method, equals('POST'));
@@ -249,11 +249,10 @@
headers: {'content-type': 'application/json'}));
});
- expect(grant.handleAuthorizationCode('auth code'),
- completion(predicate((client) {
- expect(client.credentials.accessToken, equals('access token'));
- return true;
- })));
+ expect(
+ await grant.handleAuthorizationCode('auth code'),
+ isA<oauth2.Client>().having((c) => c.credentials.accessToken,
+ 'credentials.accessToken', 'access token'));
});
});
@@ -302,7 +301,8 @@
completion(equals('access token')));
});
- test('.handleAuthorizationCode sends an authorization code request', () {
+ test('.handleAuthorizationCode sends an authorization code request',
+ () async {
grant.getAuthorizationUrl(redirectUrl);
client.expectRequest((request) {
expect(request.method, equals('POST'));
@@ -328,11 +328,10 @@
headers: {'content-type': 'application/json'}));
});
- expect(grant.handleAuthorizationCode('auth code'),
- completion(predicate((client) {
- expect(client.credentials.accessToken, equals('access token'));
- return true;
- })));
+ expect(
+ await grant.handleAuthorizationCode('auth code'),
+ isA<oauth2.Client>().having((c) => c.credentials.accessToken,
+ 'credentials.accessToken', 'access token'));
});
});
diff --git a/test/handle_access_token_response_test.dart b/test/handle_access_token_response_test.dart
index 10aef48..4561749 100644
--- a/test/handle_access_token_response_test.dart
+++ b/test/handle_access_token_response_test.dart
@@ -18,7 +18,7 @@
final DateTime startTime = DateTime.now();
oauth2.Credentials handle(http.Response response,
- {GetParameters getParameters}) =>
+ {GetParameters? getParameters}) =>
handleAccessTokenResponse(
response, tokenEndpoint, startTime, ['scope'], ' ',
getParameters: getParameters);
@@ -141,7 +141,7 @@
});
test('with no content-type causes a FormatException', () {
- expect(() => handleSuccess(contentType: null), throwsFormatException);
+ expect(() => handleSuccess(contentType: ''), throwsFormatException);
});
test('with a non-JSON content-type causes a FormatException', () {
@@ -221,7 +221,7 @@
'with expires-in sets the expiration to ten seconds earlier than the '
'server says', () {
var credentials = handleSuccess(expiresIn: 100);
- expect(credentials.expiration.millisecondsSinceEpoch,
+ expect(credentials.expiration?.millisecondsSinceEpoch,
startTime.millisecondsSinceEpoch + 90 * 1000);
});
diff --git a/test/utils.dart b/test/utils.dart
index df43af5..f66e7df 100644
--- a/test/utils.dart
+++ b/test/utils.dart
@@ -18,7 +18,7 @@
super(fn);
factory ExpectClient() {
- ExpectClient client;
+ late ExpectClient client;
client = ExpectClient._((request) => client._handleRequest(request));
return client;
}