blob: bc709197d77d6efbc50026206d429aa5bee38bcc [file] [log] [blame]
// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:google_sign_in/testing.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('GoogleSignIn', () {
const MethodChannel channel = MethodChannel(
'plugins.flutter.io/google_sign_in',
);
const Map<String, String> kUserData = <String, String>{
"email": "john.doe@gmail.com",
"id": "8162538176523816253123",
"photoUrl": "https://lh5.googleusercontent.com/photo.jpg",
"displayName": "John Doe",
};
const Map<String, dynamic> kDefaultResponses = <String, dynamic>{
'init': null,
'signInSilently': kUserData,
'signIn': kUserData,
'signOut': null,
'disconnect': null,
'isSignedIn': true,
'requestScopes': true,
'getTokens': <dynamic, dynamic>{
'idToken': '123',
'accessToken': '456',
'serverAuthCode': '789',
},
};
final List<MethodCall> log = <MethodCall>[];
Map<String, dynamic> responses;
GoogleSignIn googleSignIn;
setUp(() {
responses = Map<String, dynamic>.from(kDefaultResponses);
channel.setMockMethodCallHandler((MethodCall methodCall) {
log.add(methodCall);
final dynamic response = responses[methodCall.method];
if (response != null && response is Exception) {
return Future<dynamic>.error('$response');
}
return Future<dynamic>.value(response);
});
googleSignIn = GoogleSignIn();
log.clear();
});
test('signInSilently', () async {
await googleSignIn.signInSilently();
expect(googleSignIn.currentUser, isNotNull);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signInSilently', arguments: null),
],
);
});
test('signIn', () async {
await googleSignIn.signIn();
expect(googleSignIn.currentUser, isNotNull);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signIn', arguments: null),
],
);
});
test('signOut', () async {
await googleSignIn.signOut();
expect(googleSignIn.currentUser, isNull);
expect(log, <Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signOut', arguments: null),
]);
});
test('disconnect; null response', () async {
await googleSignIn.disconnect();
expect(googleSignIn.currentUser, isNull);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('disconnect', arguments: null),
],
);
});
test('disconnect; empty response as on iOS', () async {
responses['disconnect'] = <String, dynamic>{};
await googleSignIn.disconnect();
expect(googleSignIn.currentUser, isNull);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('disconnect', arguments: null),
],
);
});
test('isSignedIn', () async {
final bool result = await googleSignIn.isSignedIn();
expect(result, isTrue);
expect(log, <Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('isSignedIn', arguments: null),
]);
});
test('signIn works even if a previous call throws error in other zone',
() async {
responses['signInSilently'] = Exception('Not a user');
await runZonedGuarded(() async {
expect(await googleSignIn.signInSilently(), isNull);
}, (Object e, StackTrace st) {});
expect(await googleSignIn.signIn(), isNotNull);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signInSilently', arguments: null),
isMethodCall('signIn', arguments: null),
],
);
});
test('concurrent calls of the same method trigger sign in once', () async {
final List<Future<GoogleSignInAccount>> futures =
<Future<GoogleSignInAccount>>[
googleSignIn.signInSilently(),
googleSignIn.signInSilently(),
];
expect(futures.first, isNot(futures.last),
reason: 'Must return new Future');
final List<GoogleSignInAccount> users = await Future.wait(futures);
expect(googleSignIn.currentUser, isNotNull);
expect(users, <GoogleSignInAccount>[
googleSignIn.currentUser,
googleSignIn.currentUser
]);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signInSilently', arguments: null),
],
);
});
test('can sign in after previously failed attempt', () async {
responses['signInSilently'] = Exception('Not a user');
expect(await googleSignIn.signInSilently(), isNull);
expect(await googleSignIn.signIn(), isNotNull);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signInSilently', arguments: null),
isMethodCall('signIn', arguments: null),
],
);
});
test('concurrent calls of different signIn methods', () async {
final List<Future<GoogleSignInAccount>> futures =
<Future<GoogleSignInAccount>>[
googleSignIn.signInSilently(),
googleSignIn.signIn(),
];
expect(futures.first, isNot(futures.last));
final List<GoogleSignInAccount> users = await Future.wait(futures);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signInSilently', arguments: null),
],
);
expect(users.first, users.last, reason: 'Must return the same user');
expect(googleSignIn.currentUser, users.last);
});
test('can sign in after aborted flow', () async {
responses['signIn'] = null;
expect(await googleSignIn.signIn(), isNull);
responses['signIn'] = kUserData;
expect(await googleSignIn.signIn(), isNotNull);
});
test('signOut/disconnect methods always trigger native calls', () async {
final List<Future<GoogleSignInAccount>> futures =
<Future<GoogleSignInAccount>>[
googleSignIn.signOut(),
googleSignIn.signOut(),
googleSignIn.disconnect(),
googleSignIn.disconnect(),
];
await Future.wait(futures);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signOut', arguments: null),
isMethodCall('signOut', arguments: null),
isMethodCall('disconnect', arguments: null),
isMethodCall('disconnect', arguments: null),
],
);
});
test('queue of many concurrent calls', () async {
final List<Future<GoogleSignInAccount>> futures =
<Future<GoogleSignInAccount>>[
googleSignIn.signInSilently(),
googleSignIn.signOut(),
googleSignIn.signIn(),
googleSignIn.disconnect(),
];
await Future.wait(futures);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signInSilently', arguments: null),
isMethodCall('signOut', arguments: null),
isMethodCall('signIn', arguments: null),
isMethodCall('disconnect', arguments: null),
],
);
});
test('signInSilently suppresses errors by default', () async {
channel.setMockMethodCallHandler((MethodCall methodCall) {
throw "I am an error";
});
expect(await googleSignIn.signInSilently(), isNull); // should not throw
});
test('signInSilently forwards errors', () async {
channel.setMockMethodCallHandler((MethodCall methodCall) {
throw "I am an error";
});
expect(googleSignIn.signInSilently(suppressErrors: false),
throwsA(isInstanceOf<PlatformException>()));
});
test('can sign in after init failed before', () async {
int initCount = 0;
channel.setMockMethodCallHandler((MethodCall methodCall) {
if (methodCall.method == 'init') {
initCount++;
if (initCount == 1) {
throw "First init fails";
}
}
return Future<dynamic>.value(responses[methodCall.method]);
});
expect(googleSignIn.signIn(), throwsA(isInstanceOf<PlatformException>()));
expect(await googleSignIn.signIn(), isNotNull);
});
test('created with standard factory uses correct options', () async {
googleSignIn = GoogleSignIn.standard();
await googleSignIn.signInSilently();
expect(googleSignIn.currentUser, isNotNull);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signInSilently', arguments: null),
],
);
});
test('created with defaultGamesSignIn factory uses correct options',
() async {
googleSignIn = GoogleSignIn.games();
await googleSignIn.signInSilently();
expect(googleSignIn.currentUser, isNotNull);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.games',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signInSilently', arguments: null),
],
);
});
test('authentication', () async {
await googleSignIn.signIn();
log.clear();
final GoogleSignInAccount user = googleSignIn.currentUser;
final GoogleSignInAuthentication auth = await user.authentication;
expect(auth.accessToken, '456');
expect(auth.idToken, '123');
expect(auth.serverAuthCode, '789');
expect(
log,
<Matcher>[
isMethodCall('getTokens', arguments: <String, dynamic>{
'email': 'john.doe@gmail.com',
'shouldRecoverAuth': true,
}),
],
);
});
test('requestScopes returns true once new scope is granted', () async {
await googleSignIn.signIn();
final result = await googleSignIn.requestScopes(['testScope']);
expect(result, isTrue);
expect(
log,
<Matcher>[
isMethodCall('init', arguments: <String, dynamic>{
'signInOption': 'SignInOption.standard',
'scopes': <String>[],
'hostedDomain': null,
}),
isMethodCall('signIn', arguments: null),
isMethodCall('requestScopes', arguments: <String, dynamic>{
'scopes': ['testScope'],
}),
],
);
});
});
group('GoogleSignIn with fake backend', () {
const FakeUser kUserData = FakeUser(
id: "8162538176523816253123",
displayName: "John Doe",
email: "john.doe@gmail.com",
photoUrl: "https://lh5.googleusercontent.com/photo.jpg",
);
GoogleSignIn googleSignIn;
setUp(() {
final MethodChannelGoogleSignIn platformInstance =
GoogleSignInPlatform.instance;
platformInstance.channel.setMockMethodCallHandler(
(FakeSignInBackend()..user = kUserData).handleMethodCall);
googleSignIn = GoogleSignIn();
});
test('user starts as null', () async {
expect(googleSignIn.currentUser, isNull);
});
test('can sign in and sign out', () async {
await googleSignIn.signIn();
final GoogleSignInAccount user = googleSignIn.currentUser;
expect(user.displayName, equals(kUserData.displayName));
expect(user.email, equals(kUserData.email));
expect(user.id, equals(kUserData.id));
expect(user.photoUrl, equals(kUserData.photoUrl));
await googleSignIn.disconnect();
expect(googleSignIn.currentUser, isNull);
});
test('disconnect when signout already succeeds', () async {
await googleSignIn.disconnect();
expect(googleSignIn.currentUser, isNull);
});
});
}