From 36e0fa17669af310eb6cfdca97c9f4118602be06 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 13 Jan 2026 13:05:40 -0500 Subject: [PATCH 01/13] Initial conversion --- .../darwin/Tests/GoogleSignInTests.m | 839 ------------------ .../darwin/Tests/GoogleSignInTests.swift | 740 +++++++++++++++ .../ios/Runner.xcodeproj/project.pbxproj | 8 +- 3 files changed, 744 insertions(+), 843 deletions(-) delete mode 100644 packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.m create mode 100644 packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.m b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.m deleted file mode 100644 index 87bf2777c2c9..000000000000 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.m +++ /dev/null @@ -1,839 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#if TARGET_OS_OSX -@import FlutterMacOS; -#else -@import Flutter; -#endif - -@import XCTest; -@import google_sign_in_ios; -#if __has_include() -@import google_sign_in_ios.Test; -#endif -@import GoogleSignIn; - -/// Test implementation of @c FSIViewProvider. -@interface TestViewProvider : NSObject -#if TARGET_OS_OSX -/// The view containing the Flutter content. -@property(nonatomic, nullable) NSView *view; -#else -/// The view controller containing the Flutter content. -@property(nonatomic, nullable) UIViewController *viewController; -#endif -@end - -@implementation TestViewProvider -@end - -/// Test implementation of @c FSIGIDSignIn. -@interface TestSignIn : NSObject - -// To cause methods to throw an exception. -@property(nonatomic, nullable) NSException *exception; - -// Results to use in completion callbacks. -@property(nonatomic, nullable) NSObject *user; -@property(nonatomic, nullable) NSError *error; -@property(nonatomic, nullable) NSObject *signInResult; - -// Passed parameters. -@property(nonatomic, copy, nullable) NSString *hint; -@property(nonatomic, copy, nullable) NSArray *additionalScopes; -@property(nonatomic, copy, nullable) NSString *nonce; -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST -@property(nonatomic, nullable) UIViewController *presentingViewController; -#else -@property(nonatomic, nullable) NSWindow *presentingWindow; -#endif - -/// Whether @c signOut was called. -@property(nonatomic) BOOL signOutCalled; - -@end - -@implementation TestSignIn -@synthesize configuration; - -- (BOOL)handleURL:(NSURL *)url { - return YES; -} - -- (void)restorePreviousSignInWithCompletion: - (nullable void (^)(NSObject *_Nullable user, - NSError *_Nullable error))completion { - if (self.exception) { - @throw self.exception; - } - if (completion) { - completion(self.user, self.user ? nil : self.error); - } -} - -- (void)signOut { - self.signOutCalled = YES; -} - -- (void)disconnectWithCompletion:(nullable void (^)(NSError *_Nullable error))completion { - if (self.exception) { - @throw self.exception; - } - if (completion) { - completion(self.error); - } -} - -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST -- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController - hint:(nullable NSString *)hint - additionalScopes:(nullable NSArray *)additionalScopes - nonce:(nullable NSString *)nonce - completion:(nullable void (^)( - NSObject *_Nullable signInResult, - NSError *_Nullable error))completion { - if (self.exception) { - @throw self.exception; - } - self.presentingViewController = presentingViewController; - self.hint = hint; - self.additionalScopes = additionalScopes; - self.nonce = nonce; - if (completion) { - completion(self.signInResult, self.signInResult ? nil : self.error); - } -} -#else -- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow - hint:(nullable NSString *)hint - additionalScopes:(nullable NSArray *)additionalScopes - nonce:(nullable NSString *)nonce - completion: - (nullable void (^)(NSObject *_Nullable signInResult, - NSError *_Nullable error))completion { - if (self.exception) { - @throw self.exception; - } - self.presentingWindow = presentingWindow; - self.hint = hint; - self.additionalScopes = additionalScopes; - self.nonce = nonce; - if (completion) { - completion(self.signInResult, self.signInResult ? nil : self.error); - } -} -#endif - -@end - -/// Test implementation of @c FSIGIDProfileData. -@interface TestProfileData : NSObject -@property(nonatomic, readwrite) NSString *email; -@property(nonatomic, readwrite) NSString *name; -/// A URL to return from imageURLWithDimension:. -@property(nonatomic) NSURL *imageURL; -@end - -@implementation TestProfileData { -} - -- (BOOL)hasImage { - return self.imageURL != nil; -} - -- (NSURL *)imageURLWithDimension:(NSUInteger)dimension { - return self.imageURL; -} -@end - -/// Test implementation of @c FSIGIDToken. -@interface TestToken : NSObject -@property(nonatomic, readwrite) NSString *tokenString; -@property(nonatomic, readwrite) NSDate *expirationDate; -@end - -@implementation TestToken -@end - -/// Test implementation of @c FSIGIDSignInResult. -@interface TestSignInResult : NSObject -@property(nonatomic, readwrite) NSObject *user; -@property(nonatomic, readwrite, nullable) NSString *serverAuthCode; -@end - -@implementation TestSignInResult -@end - -/// Test implementation of @c FSIGIDGoogleUser. -@interface TestGoogleUser : NSObject -@property(nonatomic, readwrite, nullable) NSString *userID; -@property(nonatomic, readwrite, nullable) NSObject *profile; -@property(nonatomic, readwrite, nullable) NSArray *grantedScopes; -@property(nonatomic, readwrite) NSObject *accessToken; -@property(nonatomic, readwrite) NSObject *refreshToken; -@property(nonatomic, readwrite, nullable) NSObject *idToken; - -/// An exception to throw from methods. -@property(nonatomic, nullable) NSException *exception; - -/// The result to return from addScopes:presentingViewController:completion:. -@property(nonatomic, nullable) NSObject *result; - -/// The error to return from methods. -@property(nonatomic, nullable) NSError *error; - -// Values passed as parameters. -@property(nonatomic, copy, nullable) NSArray *requestedScopes; -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST -@property(nonatomic, nullable) UIViewController *presentingViewController; -#else -@property(nonatomic, nullable) NSWindow *presentingWindow; -#endif -@end - -@implementation TestGoogleUser - -- (void)refreshTokensIfNeededWithCompletion:(void (^)(NSObject *_Nullable user, - NSError *_Nullable error))completion { - if (self.exception) { - @throw self.exception; - } - if (completion) { - completion(self.error ? nil : self, self.error); - } -} - -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST - -- (void)addScopes:(NSArray *)scopes - presentingViewController:(UIViewController *)presentingViewController - completion:(nullable void (^)(NSObject *_Nullable result, - NSError *_Nullable error))completion { - self.requestedScopes = scopes; - self.presentingViewController = presentingViewController; - if (self.exception) { - @throw self.exception; - } - if (completion) { - completion(self.error ? nil : self.result, self.error); - } -} - -#elif TARGET_OS_OSX - -- (void)addScopes:(NSArray *)scopes - presentingWindow:(NSWindow *)presentingWindow - completion:(nullable void (^)(NSObject *_Nullable result, - NSError *_Nullable error))completion { - self.requestedScopes = scopes; - self.presentingWindow = presentingWindow; - if (self.exception) { - @throw self.exception; - } - if (completion) { - completion(self.error ? nil : self.result, self.error); - } -} - -#endif - -@end - -#pragma mark - - -@interface FLTGoogleSignInPluginTest : XCTestCase - -@property(nonatomic) TestViewProvider *viewProvider; -@property(nonatomic) FLTGoogleSignInPlugin *plugin; -@property(nonatomic) TestSignIn *fakeSignIn; -@property(nonatomic, copy) NSDictionary *googleServiceInfo; - -@end - -@implementation FLTGoogleSignInPluginTest - -- (void)setUp { - [super setUp]; - self.viewProvider = [[TestViewProvider alloc] init]; - - self.fakeSignIn = [[TestSignIn alloc] init]; - - self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.fakeSignIn - viewProvider:self.viewProvider]; - - NSString *plistPath = - [[NSBundle bundleForClass:[self class]] pathForResource:@"GoogleService-Info" - ofType:@"plist"]; - if (plistPath) { - self.googleServiceInfo = [[NSDictionary alloc] initWithContentsOfFile:plistPath]; - } -} - -- (void)testSignOut { - FlutterError *error; - [self.plugin signOutWithError:&error]; - XCTAssertTrue(self.fakeSignIn.signOutCalled); - XCTAssertNil(error); -} - -- (void)testDisconnect { - XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - [self.plugin disconnectWithCompletion:^(FlutterError *error) { - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -#pragma mark - Configure - -- (void)testInitNoClientIdNoError { - // Init plugin without GoogleService-Info.plist. - self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.fakeSignIn - viewProvider:self.viewProvider - googleServiceProperties:nil]; - - // init call does not provide a clientId. - FSIPlatformConfigurationParams *params = [FSIPlatformConfigurationParams makeWithClientId:nil - serverClientId:nil - hostedDomain:nil]; - - FlutterError *error; - [self.plugin configureWithParameters:params error:&error]; - XCTAssertNil(error); -} - -- (void)testInitGoogleServiceInfoPlist { - self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.fakeSignIn - viewProvider:self.viewProvider - googleServiceProperties:self.googleServiceInfo]; - FSIPlatformConfigurationParams *params = - [FSIPlatformConfigurationParams makeWithClientId:nil - serverClientId:nil - hostedDomain:@"example.com"]; - - FlutterError *error; - [self.plugin configureWithParameters:params error:&error]; - XCTAssertNil(error); - XCTAssertEqualObjects(self.fakeSignIn.configuration.hostedDomain, @"example.com"); - // Set in example app GoogleService-Info.plist. - XCTAssertEqualObjects( - self.fakeSignIn.configuration.clientID, - @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); - XCTAssertEqualObjects(self.fakeSignIn.configuration.serverClientID, @"YOUR_SERVER_CLIENT_ID"); -} - -- (void)testInitDynamicClientIdNullDomain { - // Init plugin without GoogleService-Info.plist. - self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.fakeSignIn - viewProvider:self.viewProvider - googleServiceProperties:nil]; - - FSIPlatformConfigurationParams *params = - [FSIPlatformConfigurationParams makeWithClientId:@"mockClientId" - serverClientId:nil - hostedDomain:nil]; - - FlutterError *error; - [self.plugin configureWithParameters:params error:&error]; - XCTAssertNil(error); - XCTAssertNil(self.fakeSignIn.configuration.hostedDomain); - XCTAssertEqualObjects(self.fakeSignIn.configuration.clientID, @"mockClientId"); - XCTAssertNil(self.fakeSignIn.configuration.serverClientID); -} - -- (void)testInitDynamicServerClientIdNullDomain { - self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.fakeSignIn - viewProvider:self.viewProvider - googleServiceProperties:self.googleServiceInfo]; - FSIPlatformConfigurationParams *params = - [FSIPlatformConfigurationParams makeWithClientId:nil - serverClientId:@"mockServerClientId" - hostedDomain:nil]; - - FlutterError *error; - [self.plugin configureWithParameters:params error:&error]; - XCTAssertNil(error); - XCTAssertNil(self.fakeSignIn.configuration.hostedDomain); - // Set in example app GoogleService-Info.plist. - XCTAssertEqualObjects( - self.fakeSignIn.configuration.clientID, - @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); - // Overridden by params. - XCTAssertEqualObjects(self.fakeSignIn.configuration.serverClientID, @"mockServerClientId"); -} - -- (void)testInitInfoPlist { - FSIPlatformConfigurationParams *params = - [FSIPlatformConfigurationParams makeWithClientId:nil - serverClientId:nil - hostedDomain:@"example.com"]; - - FlutterError *error; - [self.plugin configureWithParameters:params error:&error]; - XCTAssertNil(error); - // No configuration should be set, allowing the SDK to use its default behavior - // (which is to load configuration information from Info.plist). - XCTAssertNil(self.fakeSignIn.configuration); -} - -#pragma mark - restorePreviousSignIn - -- (void)testSignInSilently { - TestGoogleUser *fakeUser = [[TestGoogleUser alloc] init]; - fakeUser.userID = @"mockID"; - self.fakeSignIn.user = fakeUser; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin restorePreviousSignInWithCompletion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.error); - XCTAssertNotNil(result.success); - FSIUserData *user = result.success.user; - XCTAssertNil(user.displayName); - XCTAssertNil(user.email); - XCTAssertEqualObjects(user.userId, @"mockID"); - XCTAssertNil(user.photoUrl); - XCTAssertNil(result.success.accessToken); - XCTAssertNil(result.success.serverAuthCode); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRestorePreviousSignInWithError { - NSError *sdkError = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeHasNoAuthInKeychain - userInfo:nil]; - self.fakeSignIn.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin restorePreviousSignInWithCompletion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.success); - XCTAssertEqual(result.error.type, FSIGoogleSignInErrorCodeNoAuthInKeychain); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -#pragma mark - signIn - -- (void)testSignIn { - self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.fakeSignIn - viewProvider:self.viewProvider - googleServiceProperties:self.googleServiceInfo]; - TestGoogleUser *fakeUser = [[TestGoogleUser alloc] init]; - fakeUser.userID = @"mockID"; - TestProfileData *fakeUserProfile = [[TestProfileData alloc] init]; - fakeUserProfile.name = @"mockDisplay"; - fakeUserProfile.email = @"mock@example.com"; - fakeUserProfile.imageURL = [NSURL URLWithString:@"https://example.com/profile.png"]; - - NSString *accessToken = @"mockAccessToken"; - NSString *serverAuthCode = @"mockAuthCode"; - fakeUser.profile = fakeUserProfile; - TestToken *fakeAccessToken = [[TestToken alloc] init]; - fakeAccessToken.tokenString = accessToken; - fakeUser.accessToken = fakeAccessToken; - - TestSignInResult *fakeSignInResult = [[TestSignInResult alloc] init]; - fakeSignInResult.user = fakeUser; - fakeSignInResult.serverAuthCode = serverAuthCode; - - self.fakeSignIn.signInResult = fakeSignInResult; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin signInWithScopeHint:@[] - nonce:nil - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - FSIUserData *user = result.success.user; - XCTAssertEqualObjects(user.displayName, @"mockDisplay"); - XCTAssertEqualObjects(user.email, @"mock@example.com"); - XCTAssertEqualObjects(user.userId, @"mockID"); - XCTAssertEqualObjects(user.photoUrl, @"https://example.com/profile.png"); - XCTAssertEqualObjects(result.success.accessToken, accessToken); - XCTAssertEqualObjects(result.success.serverAuthCode, serverAuthCode); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testSignInWithScopeHint { - FlutterError *initializationError; - [self.plugin configureWithParameters:[FSIPlatformConfigurationParams makeWithClientId:nil - serverClientId:nil - hostedDomain:nil] - error:&initializationError]; - XCTAssertNil(initializationError); - - TestGoogleUser *fakeUser = [[TestGoogleUser alloc] init]; - fakeUser.userID = @"mockID"; - TestSignInResult *fakeSignInResult = [[TestSignInResult alloc] init]; - fakeSignInResult.user = fakeUser; - - NSArray *requestedScopes = @[ @"scope1", @"scope2" ]; - self.fakeSignIn.signInResult = fakeSignInResult; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin signInWithScopeHint:requestedScopes - nonce:nil - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.error); - XCTAssertEqualObjects(result.success.user.userId, @"mockID"); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; - - XCTAssertTrue([[NSSet setWithArray:self.fakeSignIn.additionalScopes] - isEqualToSet:[NSSet setWithArray:requestedScopes]]); -} - -- (void)testSignInWithNonce { - FlutterError *initializationError; - [self.plugin configureWithParameters:[FSIPlatformConfigurationParams makeWithClientId:nil - serverClientId:nil - hostedDomain:nil] - error:&initializationError]; - XCTAssertNil(initializationError); - - TestGoogleUser *fakeUser = [[TestGoogleUser alloc] init]; - fakeUser.userID = @"mockID"; - TestSignInResult *fakeSignInResult = [[TestSignInResult alloc] init]; - fakeSignInResult.user = fakeUser; - - NSString *nonce = @"A nonce"; - self.fakeSignIn.signInResult = fakeSignInResult; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin signInWithScopeHint:@[] - nonce:nonce - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.error); - XCTAssertEqualObjects(result.success.user.userId, @"mockID"); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; - - XCTAssertEqualObjects(self.fakeSignIn.nonce, nonce); -} - -- (void)testSignInAlreadyGranted { - TestGoogleUser *fakeUser = [[TestGoogleUser alloc] init]; - fakeUser.userID = @"mockID"; - TestSignInResult *fakeSignInResult = [[TestSignInResult alloc] init]; - fakeSignInResult.user = fakeUser; - - self.fakeSignIn.signInResult = fakeSignInResult; - - NSError *sdkError = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeScopesAlreadyGranted - userInfo:nil]; - self.fakeSignIn.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin signInWithScopeHint:@[] - nonce:nil - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.error); - XCTAssertEqualObjects(result.success.user.userId, @"mockID"); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testSignInError { - NSError *sdkError = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeCanceled - userInfo:nil]; - self.fakeSignIn.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin signInWithScopeHint:@[] - nonce:nil - completion:^(FSISignInResult *result, FlutterError *error) { - // Known errors from the SDK are returned as structured data, not - // FlutterError. - XCTAssertNil(error); - XCTAssertNil(result.success); - XCTAssertEqual(result.error.type, FSIGoogleSignInErrorCodeCanceled); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testSignInExceptionReturnsError { - self.fakeSignIn.exception = [NSException exceptionWithName:@"MockName" - reason:@"MockReason" - userInfo:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin signInWithScopeHint:@[] - nonce:nil - completion:^(FSISignInResult *result, FlutterError *error) { - // Unexpected errors, such as runtime exceptions, are returned as - // FlutterError. - XCTAssertNil(result); - XCTAssertEqualObjects(error.code, @"google_sign_in"); - XCTAssertEqualObjects(error.message, @"MockReason"); - XCTAssertEqualObjects(error.details, @"MockName"); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -#pragma mark - refreshedAuthorizationTokens - -- (void)testRefreshTokens { - TestGoogleUser *fakeUser = [self addSignedInUser]; - // TestGoogleUser passes itself as the result's user property, so set the - // fake result data on this object. - TestToken *fakeIDToken = [[TestToken alloc] init]; - fakeIDToken.tokenString = @"mockIdToken"; - fakeUser.idToken = fakeIDToken; - - TestToken *fakeAccessToken = [[TestToken alloc] init]; - fakeAccessToken.tokenString = @"mockAccessToken"; - fakeUser.accessToken = fakeAccessToken; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin - refreshedAuthorizationTokensForUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.error); - XCTAssertEqualObjects(result.success.user.idToken, @"mockIdToken"); - XCTAssertEqualObjects(result.success.accessToken, - @"mockAccessToken"); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRefreshTokensUnkownUser { - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin - refreshedAuthorizationTokensForUser:@"unknownUser" - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.success); - XCTAssertEqual(result.error.type, - FSIGoogleSignInErrorCodeUserMismatch); - XCTAssertEqualObjects(result.error.message, - @"The user is no longer signed in."); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRefreshTokensNoAuthKeychainError { - TestGoogleUser *fakeUser = [self addSignedInUser]; - - NSError *sdkError = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeHasNoAuthInKeychain - userInfo:nil]; - fakeUser.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin refreshedAuthorizationTokensForUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.success); - XCTAssertEqual(result.error.type, - FSIGoogleSignInErrorCodeNoAuthInKeychain); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRefreshTokensCancelledError { - TestGoogleUser *fakeUser = [self addSignedInUser]; - - NSError *sdkError = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeCanceled - userInfo:nil]; - fakeUser.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin refreshedAuthorizationTokensForUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.success); - XCTAssertEqual(result.error.type, - FSIGoogleSignInErrorCodeCanceled); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRefreshTokensURLError { - TestGoogleUser *fakeUser = [self addSignedInUser]; - - NSError *sdkError = [NSError errorWithDomain:NSURLErrorDomain - code:NSURLErrorTimedOut - userInfo:nil]; - fakeUser.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin refreshedAuthorizationTokensForUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(result.error); - XCTAssertNil(result.success); - NSString *expectedCode = [NSString - stringWithFormat:@"%@: %ld", NSURLErrorDomain, - NSURLErrorTimedOut]; - XCTAssertEqualObjects(error.code, expectedCode); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRefreshTokensUnknownError { - TestGoogleUser *fakeUser = [self addSignedInUser]; - - NSError *sdkError = [NSError errorWithDomain:@"BogusDomain" code:42 userInfo:nil]; - fakeUser.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin refreshedAuthorizationTokensForUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(result.success); - XCTAssertEqualObjects(error.code, @"BogusDomain: 42"); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -#pragma mark - addScopes - -- (void)testRequestScopesPassesScopes { - TestGoogleUser *fakeUser = [self addSignedInUser]; - // Create a different instance to return in the result, to avoid a retain cycle. - TestGoogleUser *fakeResultUser = [[TestGoogleUser alloc] init]; - fakeResultUser.userID = fakeUser.userID; - TestSignInResult *fakeSignInResult = [[TestSignInResult alloc] init]; - fakeSignInResult.user = fakeResultUser; - fakeUser.result = fakeSignInResult; - - NSArray *scopes = @[ @"mockScope1" ]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin addScopes:@[ @"mockScope1" ] - forUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNotNil(result.success); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; - XCTAssertEqual(fakeUser.requestedScopes.firstObject, scopes.firstObject); -} - -- (void)testRequestScopesResultErrorIfNotSignedIn { - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin addScopes:@[ @"mockScope1" ] - forUser:@"unknownUser" - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.success); - XCTAssertEqual(result.error.type, FSIGoogleSignInErrorCodeUserMismatch); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRequestScopesIfNoMissingScope { - TestGoogleUser *fakeUser = [self addSignedInUser]; - - NSError *sdkError = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeScopesAlreadyGranted - userInfo:nil]; - fakeUser.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin addScopes:@[ @"mockScope1" ] - forUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.success); - XCTAssertEqual(result.error.type, FSIGoogleSignInErrorCodeScopesAlreadyGranted); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRequestScopesResultErrorIfMismatchingUser { - TestGoogleUser *fakeUser = [self addSignedInUser]; - - NSError *sdkError = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeMismatchWithCurrentUser - userInfo:nil]; - fakeUser.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin addScopes:@[ @"mockScope1" ] - forUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(error); - XCTAssertNil(result.success); - XCTAssertEqual(result.error.type, FSIGoogleSignInErrorCodeUserMismatch); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRequestScopesWithUnknownError { - TestGoogleUser *fakeUser = [self addSignedInUser]; - - NSError *sdkError = [NSError errorWithDomain:@"BogusDomain" code:42 userInfo:nil]; - fakeUser.error = sdkError; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin addScopes:@[ @"mockScope1" ] - forUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(result); - XCTAssertEqualObjects(error.code, @"BogusDomain: 42"); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testRequestScopesException { - TestGoogleUser *fakeUser = [self addSignedInUser]; - - fakeUser.exception = [NSException exceptionWithName:@"MockName" - reason:@"MockReason" - userInfo:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - [self.plugin addScopes:@[] - forUser:fakeUser.userID - completion:^(FSISignInResult *result, FlutterError *error) { - XCTAssertNil(result); - XCTAssertEqualObjects(error.code, @"request_scopes"); - XCTAssertEqualObjects(error.message, @"MockReason"); - XCTAssertEqualObjects(error.details, @"MockName"); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -#pragma mark - Utils - -- (TestGoogleUser *)addSignedInUser { - NSString *identifier = @"fakeID"; - TestGoogleUser *user = [[TestGoogleUser alloc] init]; - user.userID = identifier; - self.plugin.usersByIdentifier[identifier] = user; - return user; -} - -@end diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift new file mode 100644 index 000000000000..2c25be54872f --- /dev/null +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -0,0 +1,740 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import GoogleSignIn +import XCTest +import google_sign_in_ios + +#if os(OSX) + import FlutterMacOS +#else + import Flutter +#endif + +// Test implementation of FSIViewProvider. +class TestViewProvider: NSObject, FSIViewProvider { + #if os(OSX) + // The view containing the Flutter content. + var view: NSView? + #else + // The view controller containing the Flutter content. + var viewController: UIViewController? + #endif +} + +// Test implementation of FSIGIDSignIn. +class TestSignIn: NSObject, FSIGIDSignIn { + var configuration: GIDConfiguration? + + // To cause methods to throw an exception. + var exception: NSException? + + // Results to use in completion callbacks. + var user: FSIGIDGoogleUser? + var error: Error? + var signInResult: FSIGIDSignInResult? + + // Passed parameters. + var hint: String? + var additionalScopes: [String]? + var nonce: String? + #if os(iOS) || targetEnvironment(macCatalyst) + var presentingViewController: UIViewController? + #else + var presentingWindow: NSWindow? + #endif + + // Whether signOut was called. + var signOutCalled = false + + func handle(_ url: URL) -> Bool { + return true + } + + func restorePreviousSignIn(completion: ((FSIGIDGoogleUser?, Error?) -> Void)?) { + if let exception = exception { + exception.raise() + } + completion?(user, user != nil ? nil : error) + } + + func signOut() { + signOutCalled = true + } + + func disconnect(completion: ((Error?) -> Void)?) { + if let exception = exception { + exception.raise() + } + completion?(error) + } + + #if os(iOS) || targetEnvironment(macCatalyst) + func signIn( + withPresenting presentingViewController: UIViewController, + hint: String?, + additionalScopes: [String]?, + nonce: String?, + completion: ((FSIGIDSignInResult?, Error?) -> Void)? + ) { + if let exception = exception { + exception.raise() + } + self.presentingViewController = presentingViewController + self.hint = hint + self.additionalScopes = additionalScopes + self.nonce = nonce + completion?(signInResult, signInResult != nil ? nil : error) + } + #else + func signIn( + withPresenting presentingWindow: NSWindow, + hint: String?, + additionalScopes: [String]?, + nonce: String?, + completion: ((FSIGIDSignInResult?, Error?) -> Void)? + ) { + if let exception = exception { + exception.raise() + } + self.presentingWindow = presentingWindow + self.hint = hint + self.additionalScopes = additionalScopes + self.nonce = nonce + completion?(signInResult, signInResult != nil ? nil : error) + } + #endif +} + +// Test implementation of FSIGIDProfileData. +class TestProfileData: NSObject, FSIGIDProfileData { + var email: String + var name: String + // A URL to return from imageURLWithDimension:. + var imageURL: URL? + + init(name: String, email: String, imageURL: URL?) { + self.name = name + self.email = email + self.imageURL = imageURL + } + + var hasImage: Bool { + return imageURL != nil + } + + func imageURL(withDimension dimension: UInt) -> URL? { + return imageURL + } +} + +// Test implementation of FSIGIDToken. +class TestToken: NSObject, FSIGIDToken { + var tokenString: String + var expirationDate: Date? + + init(_ token: String) { + tokenString = token + } +} + +// Test implementation of FSIGIDSignInResult. +class TestSignInResult: NSObject, FSIGIDSignInResult { + var user: FSIGIDGoogleUser + var serverAuthCode: String? + + init(user: FSIGIDGoogleUser, serverAuthCode: String? = nil) { + self.user = user + self.serverAuthCode = serverAuthCode + } +} + +// Test implementation of FSIGIDGoogleUser. +class TestGoogleUser: NSObject, FSIGIDGoogleUser { + var userID: String? + var profile: FSIGIDProfileData? + var grantedScopes: [String]? + var accessToken: FSIGIDToken = TestToken("Acces") + var refreshToken: FSIGIDToken = TestToken("Refresh") + var idToken: FSIGIDToken? + + // An exception to throw from methods. + var exception: NSException? + + // The result to return from addScopes:presentingViewController:completion:. + var result: FSIGIDSignInResult? + + // The error to return from methods. + var error: Error? + + // Values passed as parameters. + var requestedScopes: [String]? + #if os(iOS) || targetEnvironment(macCatalyst) + var presentingViewController: UIViewController? + #else + var presentingWindow: NSWindow? + #endif + + init(_ userIdentifier: String) { + userID = userIdentifier + } + + func refreshTokensIfNeeded(completion: @escaping (FSIGIDGoogleUser?, Error?) -> Void) { + if let exception = exception { + exception.raise() + } + completion(self.error == nil ? self : nil, self.error) + } + + #if os(iOS) || targetEnvironment(macCatalyst) + func addScopes( + _ scopes: [String], + presenting presentingViewController: UIViewController, + completion: ((FSIGIDSignInResult?, Error?) -> Void)? + ) { + self.requestedScopes = scopes + self.presentingViewController = presentingViewController + if let exception = exception { + exception.raise() + } + completion?(self.error == nil ? self.result : nil, self.error) + } + #elseif os(OSX) + func addScopes( + _ scopes: [String], + presenting presentingWindow: NSWindow, + completion: ((FSIGIDSignInResult?, Error?) -> Void)? + ) { + self.requestedScopes = scopes + self.presentingWindow = presentingWindow + if let exception = exception { + exception.raise() + } + completion?(self.error == nil ? self.result : nil, self.error) + } + #endif +} + +class FLTGoogleSignInPluginTest: XCTestCase { + + var viewProvider: TestViewProvider! + var plugin: FLTGoogleSignInPlugin! + var fakeSignIn: TestSignIn! + var googleServiceInfo: [String: Any]? + + override func setUp() { + super.setUp() + viewProvider = TestViewProvider() + fakeSignIn = TestSignIn() + plugin = FLTGoogleSignInPlugin(signIn: fakeSignIn, viewProvider: viewProvider) + + if let plistPath = Bundle(for: type(of: self)).path( + forResource: "GoogleService-Info", ofType: "plist") + { + googleServiceInfo = NSDictionary(contentsOfFile: plistPath) as? [String: Any] + } + } + + func testSignOut() { + var error: FlutterError? + plugin.signOutWithError(&error) + XCTAssertTrue(fakeSignIn.signOutCalled) + XCTAssertNil(error) + } + + func testDisconnect() { + let expectation = self.expectation(description: "expect result returns true") + plugin.disconnect { error in + XCTAssertNil(error) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + // MARK: - Configure + + func testInitNoClientIdNoError() { + // Init plugin without GoogleService-Info.plist. + plugin = FLTGoogleSignInPlugin( + signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: nil) + + // init call does not provide a clientId. + let params = FSIPlatformConfigurationParams.make( + withClientId: nil, + serverClientId: nil, + hostedDomain: nil) + + var error: FlutterError? + plugin.configure(withParameters: params, error: &error) + XCTAssertNil(error) + } + + func testInitGoogleServiceInfoPlist() { + plugin = FLTGoogleSignInPlugin( + signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: googleServiceInfo) + let params = FSIPlatformConfigurationParams.make( + withClientId: nil, + serverClientId: nil, + hostedDomain: "example.com") + + var error: FlutterError? + plugin.configure(withParameters: params, error: &error) + XCTAssertNil(error) + XCTAssertEqual(fakeSignIn.configuration?.hostedDomain, "example.com") + // Set in example app GoogleService-Info.plist. + XCTAssertEqual( + fakeSignIn.configuration?.clientID, + "479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com") + XCTAssertEqual(fakeSignIn.configuration?.serverClientID, "YOUR_SERVER_CLIENT_ID") + } + + func testInitDynamicClientIdNullDomain() { + // Init plugin without GoogleService-Info.plist. + plugin = FLTGoogleSignInPlugin( + signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: nil) + + let params = FSIPlatformConfigurationParams.make( + withClientId: "mockClientId", + serverClientId: nil, + hostedDomain: nil) + + var error: FlutterError? + plugin.configure(withParameters: params, error: &error) + XCTAssertNil(error) + XCTAssertNil(fakeSignIn.configuration?.hostedDomain) + XCTAssertEqual(fakeSignIn.configuration?.clientID, "mockClientId") + XCTAssertNil(fakeSignIn.configuration?.serverClientID) + } + + func testInitDynamicServerClientIdNullDomain() { + // Init plugin without GoogleService-Info.plist. + plugin = FLTGoogleSignInPlugin( + signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: googleServiceInfo) + let params = FSIPlatformConfigurationParams.make( + withClientId: nil, + serverClientId: "mockServerClientId", + hostedDomain: nil) + + var error: FlutterError? + plugin.configure(withParameters: params, error: &error) + XCTAssertNil(error) + XCTAssertNil(fakeSignIn.configuration?.hostedDomain) + // Set in example app GoogleService-Info.plist. + XCTAssertEqual( + fakeSignIn.configuration?.clientID, + "479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com") + // Overridden by params. + XCTAssertEqual(fakeSignIn.configuration?.serverClientID, "mockServerClientId") + } + + func testInitInfoPlist() { + let params = FSIPlatformConfigurationParams.make( + withClientId: nil, + serverClientId: nil, + hostedDomain: "example.com") + + var error: FlutterError? + plugin.configure(withParameters: params, error: &error) + XCTAssertNil(error) + // No configuration should be set, allowing the SDK to use its default behavior + // (which is to load configuration information from Info.plist). + XCTAssertNil(fakeSignIn.configuration) + } + + // MARK: - restorePreviousSignIn + + func testSignInSilently() { + let fakeUser = TestGoogleUser("mockID") + fakeSignIn.user = fakeUser + + let expectation = self.expectation(description: "completion called") + plugin.restorePreviousSignIn { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.error) + XCTAssertNotNil(result?.success) + let user = result?.success?.user + XCTAssertNil(user?.displayName) + XCTAssertNil(user?.email) + XCTAssertEqual(user?.userId, "mockID") + XCTAssertNil(user?.photoUrl) + XCTAssertNil(result?.success?.accessToken) + XCTAssertNil(result?.success?.serverAuthCode) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRestorePreviousSignInWithError() { + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.hasNoAuthInKeychain.rawValue, + userInfo: nil) + fakeSignIn.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.restorePreviousSignIn { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.success) + XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.noAuthInKeychain) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + // MARK: - signIn + + func testSignIn() { + plugin = FLTGoogleSignInPlugin( + signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: googleServiceInfo) + let fakeUser = TestGoogleUser("mockID") + let fakeUserProfile = TestProfileData( + name: "mockDisplay", email: "mock@example.com", + imageURL: URL(string: "https://example.com/profile.png")) + + let accessToken = "mockAccessToken" + let serverAuthCode = "mockAuthCode" + fakeUser.profile = fakeUserProfile + fakeUser.accessToken = TestToken(accessToken) + + let fakeSignInResult = TestSignInResult(user: fakeUser, serverAuthCode: serverAuthCode) + + fakeSignIn.signInResult = fakeSignInResult + + let expectation = self.expectation(description: "completion called") + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + XCTAssertNil(error) + let user = result?.success?.user + XCTAssertEqual(user?.displayName, "mockDisplay") + XCTAssertEqual(user?.email, "mock@example.com") + XCTAssertEqual(user?.userId, "mockID") + XCTAssertEqual(user?.photoUrl, "https://example.com/profile.png") + XCTAssertEqual(result?.success?.accessToken, accessToken) + XCTAssertEqual(result?.success?.serverAuthCode, serverAuthCode) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testSignInWithScopeHint() { + var initializationError: FlutterError? + plugin.configure( + withParameters: FSIPlatformConfigurationParams.make( + withClientId: nil, + serverClientId: nil, + hostedDomain: nil), + error: &initializationError) + XCTAssertNil(initializationError) + + let fakeUser = TestGoogleUser("mockID") + let fakeSignInResult = TestSignInResult(user: fakeUser) + + let requestedScopes = ["scope1", "scope2"] + fakeSignIn.signInResult = fakeSignInResult + + let expectation = self.expectation(description: "completion called") + plugin.signIn(withScopeHint: requestedScopes, nonce: nil) { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.error) + XCTAssertEqual(result?.success?.user.userId, "mockID") + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + + XCTAssertEqual(Set(fakeSignIn.additionalScopes ?? []), Set(requestedScopes)) + } + + func testSignInWithNonce() { + var initializationError: FlutterError? + plugin.configure( + withParameters: FSIPlatformConfigurationParams.make( + withClientId: nil, + serverClientId: nil, + hostedDomain: nil), + error: &initializationError) + XCTAssertNil(initializationError) + + let fakeUser = TestGoogleUser("mockID") + let fakeSignInResult = TestSignInResult(user: fakeUser) + + let nonce = "A nonce" + fakeSignIn.signInResult = fakeSignInResult + + let expectation = self.expectation(description: "completion called") + plugin.signIn(withScopeHint: [], nonce: nonce) { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.error) + XCTAssertEqual(result?.success?.user.userId, "mockID") + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + + XCTAssertEqual(fakeSignIn.nonce, nonce) + } + + func testSignInAlreadyGranted() { + let fakeUser = TestGoogleUser("mockID") + let fakeSignInResult = TestSignInResult(user: fakeUser) + + fakeSignIn.signInResult = fakeSignInResult + + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.scopesAlreadyGranted.rawValue, + userInfo: nil) + fakeSignIn.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.error) + XCTAssertEqual(result?.success?.user.userId, "mockID") + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testSignInError() { + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.canceled.rawValue, userInfo: nil) + fakeSignIn.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + // Known errors from the SDK are returned as structured data, not + // FlutterError. + XCTAssertNil(error) + XCTAssertNil(result?.success) + XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.canceled) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testSignInExceptionReturnsError() { + fakeSignIn.exception = NSException( + name: NSExceptionName(rawValue: "MockName"), + reason: "MockReason", + userInfo: nil) + + let expectation = self.expectation(description: "completion called") + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + // Unexpected errors, such as runtime exceptions, are returned as + // FlutterError. + XCTAssertNil(result) + XCTAssertEqual(error?.code, "google_sign_in") + XCTAssertEqual(error?.message, "MockReason") + XCTAssertEqual(error?.details as! String, "MockName") + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + // MARK: - refreshedAuthorizationTokens + + func testRefreshTokens() { + let fakeUser = addSignedInUser() + // TestGoogleUser passes itself as the result's user property, so set the + // fake result data on this object. + fakeUser.idToken = TestToken("mockIdToken") + fakeUser.accessToken = TestToken("mockAccessToken") + + let expectation = self.expectation(description: "completion called") + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.error) + XCTAssertEqual(result?.success?.user.idToken, "mockIdToken") + XCTAssertEqual(result?.success?.accessToken, "mockAccessToken") + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRefreshTokensUnkownUser() { + let expectation = self.expectation(description: "completion called") + plugin.refreshedAuthorizationTokens(forUser: "unknownUser") { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.success) + XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.userMismatch) + XCTAssertEqual(result?.error?.message, "The user is no longer signed in.") + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRefreshTokensNoAuthKeychainError() { + let fakeUser = addSignedInUser() + + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.hasNoAuthInKeychain.rawValue, + userInfo: nil) + fakeUser.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.success) + XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.noAuthInKeychain) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRefreshTokensCancelledError() { + let fakeUser = addSignedInUser() + + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.canceled.rawValue, userInfo: nil) + fakeUser.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.success) + XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.canceled) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRefreshTokensURLError() { + let fakeUser = addSignedInUser() + + let sdkError = NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil) + fakeUser.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + XCTAssertNil(result?.error) + XCTAssertNil(result?.success) + let expectedCode = "\(NSURLErrorDomain): \(NSURLErrorTimedOut)" + XCTAssertEqual(error?.code, expectedCode) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRefreshTokensUnknownError() { + let fakeUser = addSignedInUser() + + let sdkError = NSError(domain: "BogusDomain", code: 42, userInfo: nil) + fakeUser.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + XCTAssertNil(result?.success) + XCTAssertEqual(error?.code, "BogusDomain: 42") + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + // MARK: - addScopes + + func testRequestScopesPassesScopes() { + let fakeUser = addSignedInUser() + // Create a different instance to return in the result, to avoid a retain cycle. + let fakeResultUser = TestGoogleUser(fakeUser.userID!) + let fakeSignInResult = TestSignInResult(user: fakeResultUser) + fakeUser.result = fakeSignInResult + + let scopes = ["mockScope1"] + + let expectation = self.expectation(description: "completion called") + plugin.addScopes(scopes, forUser: fakeUser.userID!) { result, error in + XCTAssertNil(error) + XCTAssertNotNil(result?.success) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + XCTAssertEqual(fakeUser.requestedScopes?.first, scopes.first) + } + + func testRequestScopesResultErrorIfNotSignedIn() { + let expectation = self.expectation(description: "completion called") + plugin.addScopes(["mockScope1"], forUser: "unknownUser") { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.success) + XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.userMismatch) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRequestScopesIfNoMissingScope() { + let fakeUser = addSignedInUser() + + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.scopesAlreadyGranted.rawValue, + userInfo: nil) + fakeUser.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.success) + XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.scopesAlreadyGranted) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRequestScopesResultErrorIfMismatchingUser() { + let fakeUser = addSignedInUser() + + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.mismatchWithCurrentUser.rawValue, + userInfo: nil) + fakeUser.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in + XCTAssertNil(error) + XCTAssertNil(result?.success) + XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.userMismatch) + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRequestScopesWithUnknownError() { + let fakeUser = addSignedInUser() + + let sdkError = NSError(domain: "BogusDomain", code: 42, userInfo: nil) + fakeUser.error = sdkError + + let expectation = self.expectation(description: "completion called") + plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in + XCTAssertNil(result) + XCTAssertEqual(error?.code, "BogusDomain: 42") + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRequestScopesException() { + let fakeUser = addSignedInUser() + + fakeUser.exception = NSException( + name: NSExceptionName(rawValue: "MockName"), + reason: "MockReason", + userInfo: nil) + + let expectation = self.expectation(description: "completion called") + plugin.addScopes([], forUser: fakeUser.userID!) { result, error in + XCTAssertNil(result) + XCTAssertEqual(error?.code, "request_scopes") + XCTAssertEqual(error?.message, "MockReason") + XCTAssertEqual(error?.details as! String, "MockName") + expectation.fulfill() + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + // MARK: - Utils + + func addSignedInUser() -> TestGoogleUser { + let identifier = "fakeID" + let user = TestGoogleUser(identifier) + plugin.usersByIdentifier[identifier] = user + return user + } +} diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj index 6c78727f6d7c..b8f5a9ea3f77 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -18,7 +18,7 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; C2FB9CBA01DB0A2DE5F31E12 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */; }; C56D3B06A42F3B35C1F47A43 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 18AD6475292B9C45B529DDC9 /* libPods-RunnerTests.a */; }; - F76AC1A52666D0540040C8BC /* GoogleSignInTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F76AC1A42666D0540040C8BC /* GoogleSignInTests.m */; }; + F76AC1A52666D0540040C8BC /* GoogleSignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76AC1A42666D0540040C8BC /* GoogleSignInTests.swift */; }; F76AC1B32666D0610040C8BC /* GoogleSignInUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F76AC1B22666D0610040C8BC /* GoogleSignInUITests.m */; }; /* End PBXBuildFile section */ @@ -75,7 +75,7 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F582639B44581540871D9BB0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; F76AC1A22666D0540040C8BC /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - F76AC1A42666D0540040C8BC /* GoogleSignInTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = GoogleSignInTests.m; path = ../../darwin/Tests/GoogleSignInTests.m; sourceTree = SOURCE_ROOT; }; + F76AC1A42666D0540040C8BC /* GoogleSignInTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GoogleSignInTests.swift; path = ../../darwin/Tests/GoogleSignInTests.swift; sourceTree = SOURCE_ROOT; }; F76AC1A62666D0540040C8BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F76AC1B02666D0610040C8BC /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F76AC1B22666D0610040C8BC /* GoogleSignInUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleSignInUITests.m; sourceTree = ""; }; @@ -192,7 +192,7 @@ isa = PBXGroup; children = ( 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, - F76AC1A42666D0540040C8BC /* GoogleSignInTests.m */, + F76AC1A42666D0540040C8BC /* GoogleSignInTests.swift */, F76AC1A62666D0540040C8BC /* Info.plist */, ); path = RunnerTests; @@ -436,7 +436,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F76AC1A52666D0540040C8BC /* GoogleSignInTests.m in Sources */, + F76AC1A52666D0540040C8BC /* GoogleSignInTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From d4592e0f72f17422bd9bd5f96e91989b73ede2dc Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 13 Jan 2026 13:42:37 -0500 Subject: [PATCH 02/13] Minor fixup, adopt Swift 6 --- .../darwin/Tests/GoogleSignInTests.swift | 100 +++++++++--------- .../ios/Flutter/AppFrameworkInfo.plist | 2 - .../ios/Runner.xcodeproj/project.pbxproj | 10 +- .../macos/Runner.xcodeproj/project.pbxproj | 16 +-- 4 files changed, 68 insertions(+), 60 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift index 2c25be54872f..81b90c553337 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -243,13 +243,13 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertNil(error) } - func testDisconnect() { + func testDisconnect() async { let expectation = self.expectation(description: "expect result returns true") plugin.disconnect { error in XCTAssertNil(error) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - Configure @@ -344,8 +344,12 @@ class FLTGoogleSignInPluginTest: XCTestCase { // MARK: - restorePreviousSignIn - func testSignInSilently() { + func testSignInSilently() async { let fakeUser = TestGoogleUser("mockID") + let fakeUserProfile = TestProfileData( + name: "mockDisplay", email: "mock@example.com", + imageURL: URL(string: "https://example.com/profile.png")) + fakeUser.profile = fakeUserProfile fakeSignIn.user = fakeUser let expectation = self.expectation(description: "completion called") @@ -354,18 +358,18 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertNil(result?.error) XCTAssertNotNil(result?.success) let user = result?.success?.user - XCTAssertNil(user?.displayName) - XCTAssertNil(user?.email) - XCTAssertEqual(user?.userId, "mockID") - XCTAssertNil(user?.photoUrl) - XCTAssertNil(result?.success?.accessToken) - XCTAssertNil(result?.success?.serverAuthCode) + XCTAssertEqual(user?.displayName, fakeUserProfile.name) + XCTAssertEqual(user?.email, fakeUserProfile.email) + XCTAssertEqual(user?.userId, fakeUser.userID) + XCTAssertEqual(user?.photoUrl, fakeUserProfile.imageURL?.absoluteString) + XCTAssertEqual(result?.success?.accessToken, fakeUser.accessToken.tokenString) + XCTAssertNil(result?.success?.serverAuthCode) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRestorePreviousSignInWithError() { + func testRestorePreviousSignInWithError() async { let sdkError = NSError( domain: kGIDSignInErrorDomain, code: GIDSignInError.hasNoAuthInKeychain.rawValue, userInfo: nil) @@ -378,12 +382,12 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.noAuthInKeychain) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - signIn - func testSignIn() { + func testSignIn() async { plugin = FLTGoogleSignInPlugin( signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: googleServiceInfo) let fakeUser = TestGoogleUser("mockID") @@ -412,10 +416,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.success?.serverAuthCode, serverAuthCode) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testSignInWithScopeHint() { + func testSignInWithScopeHint() async { var initializationError: FlutterError? plugin.configure( withParameters: FSIPlatformConfigurationParams.make( @@ -438,12 +442,12 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.success?.user.userId, "mockID") expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) XCTAssertEqual(Set(fakeSignIn.additionalScopes ?? []), Set(requestedScopes)) } - func testSignInWithNonce() { + func testSignInWithNonce() async { var initializationError: FlutterError? plugin.configure( withParameters: FSIPlatformConfigurationParams.make( @@ -466,12 +470,12 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.success?.user.userId, "mockID") expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) XCTAssertEqual(fakeSignIn.nonce, nonce) } - func testSignInAlreadyGranted() { + func testSignInAlreadyGranted() async { let fakeUser = TestGoogleUser("mockID") let fakeSignInResult = TestSignInResult(user: fakeUser) @@ -489,10 +493,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.success?.user.userId, "mockID") expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testSignInError() { + func testSignInError() async { let sdkError = NSError( domain: kGIDSignInErrorDomain, code: GIDSignInError.canceled.rawValue, userInfo: nil) fakeSignIn.error = sdkError @@ -506,10 +510,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.canceled) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testSignInExceptionReturnsError() { + func testSignInExceptionReturnsError() async { fakeSignIn.exception = NSException( name: NSExceptionName(rawValue: "MockName"), reason: "MockReason", @@ -525,12 +529,12 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(error?.details as! String, "MockName") expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - refreshedAuthorizationTokens - func testRefreshTokens() { + func testRefreshTokens() async { let fakeUser = addSignedInUser() // TestGoogleUser passes itself as the result's user property, so set the // fake result data on this object. @@ -545,10 +549,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.success?.accessToken, "mockAccessToken") expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensUnkownUser() { + func testRefreshTokensUnkownUser() async { let expectation = self.expectation(description: "completion called") plugin.refreshedAuthorizationTokens(forUser: "unknownUser") { result, error in XCTAssertNil(error) @@ -557,10 +561,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.error?.message, "The user is no longer signed in.") expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensNoAuthKeychainError() { + func testRefreshTokensNoAuthKeychainError() async { let fakeUser = addSignedInUser() let sdkError = NSError( @@ -575,10 +579,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.noAuthInKeychain) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensCancelledError() { + func testRefreshTokensCancelledError() async { let fakeUser = addSignedInUser() let sdkError = NSError( @@ -592,10 +596,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.canceled) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensURLError() { + func testRefreshTokensURLError() async { let fakeUser = addSignedInUser() let sdkError = NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil) @@ -609,10 +613,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(error?.code, expectedCode) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensUnknownError() { + func testRefreshTokensUnknownError() async { let fakeUser = addSignedInUser() let sdkError = NSError(domain: "BogusDomain", code: 42, userInfo: nil) @@ -624,12 +628,12 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(error?.code, "BogusDomain: 42") expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - addScopes - func testRequestScopesPassesScopes() { + func testRequestScopesPassesScopes() async { let fakeUser = addSignedInUser() // Create a different instance to return in the result, to avoid a retain cycle. let fakeResultUser = TestGoogleUser(fakeUser.userID!) @@ -644,11 +648,11 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertNotNil(result?.success) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) XCTAssertEqual(fakeUser.requestedScopes?.first, scopes.first) } - func testRequestScopesResultErrorIfNotSignedIn() { + func testRequestScopesResultErrorIfNotSignedIn() async { let expectation = self.expectation(description: "completion called") plugin.addScopes(["mockScope1"], forUser: "unknownUser") { result, error in XCTAssertNil(error) @@ -656,10 +660,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.userMismatch) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRequestScopesIfNoMissingScope() { + func testRequestScopesIfNoMissingScope() async { let fakeUser = addSignedInUser() let sdkError = NSError( @@ -674,10 +678,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.scopesAlreadyGranted) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRequestScopesResultErrorIfMismatchingUser() { + func testRequestScopesResultErrorIfMismatchingUser() async { let fakeUser = addSignedInUser() let sdkError = NSError( @@ -692,10 +696,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.userMismatch) expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRequestScopesWithUnknownError() { + func testRequestScopesWithUnknownError() async { let fakeUser = addSignedInUser() let sdkError = NSError(domain: "BogusDomain", code: 42, userInfo: nil) @@ -707,10 +711,10 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(error?.code, "BogusDomain: 42") expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } - func testRequestScopesException() { + func testRequestScopesException() async { let fakeUser = addSignedInUser() fakeUser.exception = NSException( @@ -726,7 +730,7 @@ class FLTGoogleSignInPluginTest: XCTestCase { XCTAssertEqual(error?.details as! String, "MockName") expectation.fulfill() } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - Utils diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Flutter/AppFrameworkInfo.plist b/packages/google_sign_in/google_sign_in_ios/example/ios/Flutter/AppFrameworkInfo.plist index 1f6b98f117b2..6fe4034356ac 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/Flutter/AppFrameworkInfo.plist @@ -24,7 +24,5 @@ arm64 - MinimumOSVersion - 13.0 diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj index b8f5a9ea3f77..f262e2f80887 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -60,6 +60,7 @@ 5A76713E622F06379AEDEBFA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 5C6F5A6C1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../darwin/Tests/GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; }; 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -124,6 +125,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -305,7 +307,7 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; @@ -651,6 +653,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; @@ -669,6 +672,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; @@ -747,7 +751,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; diff --git a/packages/google_sign_in/google_sign_in_ios/example/macos/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in_ios/example/macos/Runner.xcodeproj/project.pbxproj index 99e3f601ab4f..b24e24ef2b03 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in_ios/example/macos/Runner.xcodeproj/project.pbxproj @@ -22,7 +22,7 @@ /* Begin PBXBuildFile section */ 330B3F8D2B1E2A5800E6DC3F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 330B3F8C2B1E2A5800E6DC3F /* GoogleService-Info.plist */; }; - 331C80D8294CF71000263BE5 /* GoogleSignInTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* GoogleSignInTests.m */; }; + 331C80D8294CF71000263BE5 /* GoogleSignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* GoogleSignInTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; @@ -68,7 +68,7 @@ 0E1002C336EBB758B6DD1BE4 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 330B3F8C2B1E2A5800E6DC3F /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../darwin/Tests/GoogleService-Info.plist"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 331C80D7294CF71000263BE5 /* GoogleSignInTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = GoogleSignInTests.m; path = ../../darwin/Tests/GoogleSignInTests.m; sourceTree = SOURCE_ROOT; }; + 331C80D7294CF71000263BE5 /* GoogleSignInTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GoogleSignInTests.swift; path = ../../darwin/Tests/GoogleSignInTests.swift; sourceTree = SOURCE_ROOT; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* google_sign_in_ios_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = google_sign_in_ios_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -84,6 +84,7 @@ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 4626AA70E7BDF77ABE2E7DB0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 8403A54CFF539C6D4BFB2536 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; @@ -118,7 +119,7 @@ isa = PBXGroup; children = ( 330B3F8C2B1E2A5800E6DC3F /* GoogleService-Info.plist */, - 331C80D7294CF71000263BE5 /* GoogleSignInTests.m */, + 331C80D7294CF71000263BE5 /* GoogleSignInTests.swift */, ); name = RunnerTests; path = Runner; @@ -170,6 +171,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -424,7 +426,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 331C80D8294CF71000263BE5 /* GoogleSignInTests.m in Sources */, + 331C80D8294CF71000263BE5 /* GoogleSignInTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -476,7 +478,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleSignInIosExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/google_sign_in_ios_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/google_sign_in_ios_example"; }; name = Debug; @@ -491,7 +493,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleSignInIosExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/google_sign_in_ios_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/google_sign_in_ios_example"; }; name = Release; @@ -506,7 +508,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleSignInIosExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/google_sign_in_ios_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/google_sign_in_ios_example"; }; name = Profile; From dde005173eb7922a7c089ff895b832ed3c86559f Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 13 Jan 2026 16:18:58 -0500 Subject: [PATCH 03/13] Convert to Swift Testing --- .../darwin/Tests/GoogleSignInTests.swift | 539 +++++++++--------- 1 file changed, 278 insertions(+), 261 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift index 81b90c553337..59edf9a5af14 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -3,7 +3,7 @@ // found in the LICENSE file. import GoogleSignIn -import XCTest +import Testing import google_sign_in_ios #if os(OSX) @@ -216,48 +216,33 @@ class TestGoogleUser: NSObject, FSIGIDGoogleUser { #endif } -class FLTGoogleSignInPluginTest: XCTestCase { +struct FLTGoogleSignInPluginTest { + // A fake that replaces the real global GoogleSignIn instance for tests. + var fakeSignIn = TestSignIn() - var viewProvider: TestViewProvider! - var plugin: FLTGoogleSignInPlugin! - var fakeSignIn: TestSignIn! - var googleServiceInfo: [String: Any]? - - override func setUp() { - super.setUp() - viewProvider = TestViewProvider() - fakeSignIn = TestSignIn() - plugin = FLTGoogleSignInPlugin(signIn: fakeSignIn, viewProvider: viewProvider) - - if let plistPath = Bundle(for: type(of: self)).path( - forResource: "GoogleService-Info", ofType: "plist") - { - googleServiceInfo = NSDictionary(contentsOfFile: plistPath) as? [String: Any] - } - } - - func testSignOut() { + @Test func signOut() { + let plugin = createTestPlugin() var error: FlutterError? plugin.signOutWithError(&error) - XCTAssertTrue(fakeSignIn.signOutCalled) - XCTAssertNil(error) + #expect(fakeSignIn.signOutCalled == true) + #expect(error == nil) } - func testDisconnect() async { - let expectation = self.expectation(description: "expect result returns true") - plugin.disconnect { error in - XCTAssertNil(error) - expectation.fulfill() + @Test func disconnect() async { + let plugin = createTestPlugin() + await confirmation("expect result returns true") { confirmed in + plugin.disconnect { error in + #expect(error == nil) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - Configure - func testInitNoClientIdNoError() { + @Test func initNoClientIdNoError() { // Init plugin without GoogleService-Info.plist. - plugin = FLTGoogleSignInPlugin( - signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: nil) + let plugin = createTestPlugin(googleServiceProperties: nil) // init call does not provide a clientId. let params = FSIPlatformConfigurationParams.make( @@ -267,12 +252,11 @@ class FLTGoogleSignInPluginTest: XCTestCase { var error: FlutterError? plugin.configure(withParameters: params, error: &error) - XCTAssertNil(error) + #expect(error == nil) } - func testInitGoogleServiceInfoPlist() { - plugin = FLTGoogleSignInPlugin( - signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: googleServiceInfo) + @Test func initGoogleServiceInfoPlist() { + let plugin = createTestPlugin(googleServiceProperties: loadGoogleServiceInfo()) let params = FSIPlatformConfigurationParams.make( withClientId: nil, serverClientId: nil, @@ -280,19 +264,18 @@ class FLTGoogleSignInPluginTest: XCTestCase { var error: FlutterError? plugin.configure(withParameters: params, error: &error) - XCTAssertNil(error) - XCTAssertEqual(fakeSignIn.configuration?.hostedDomain, "example.com") + #expect(error == nil) + #expect(fakeSignIn.configuration?.hostedDomain == "example.com") // Set in example app GoogleService-Info.plist. - XCTAssertEqual( - fakeSignIn.configuration?.clientID, - "479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com") - XCTAssertEqual(fakeSignIn.configuration?.serverClientID, "YOUR_SERVER_CLIENT_ID") + #expect( + fakeSignIn.configuration?.clientID + == "479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com") + #expect(fakeSignIn.configuration?.serverClientID == "YOUR_SERVER_CLIENT_ID") } - func testInitDynamicClientIdNullDomain() { + @Test func initDynamicClientIdNullDomain() { // Init plugin without GoogleService-Info.plist. - plugin = FLTGoogleSignInPlugin( - signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: nil) + let plugin = createTestPlugin(googleServiceProperties: nil) let params = FSIPlatformConfigurationParams.make( withClientId: "mockClientId", @@ -301,16 +284,15 @@ class FLTGoogleSignInPluginTest: XCTestCase { var error: FlutterError? plugin.configure(withParameters: params, error: &error) - XCTAssertNil(error) - XCTAssertNil(fakeSignIn.configuration?.hostedDomain) - XCTAssertEqual(fakeSignIn.configuration?.clientID, "mockClientId") - XCTAssertNil(fakeSignIn.configuration?.serverClientID) + #expect(error == nil) + #expect(fakeSignIn.configuration?.hostedDomain == nil) + #expect(fakeSignIn.configuration?.clientID == "mockClientId") + #expect(fakeSignIn.configuration?.serverClientID == nil) } - func testInitDynamicServerClientIdNullDomain() { + @Test func initDynamicServerClientIdNullDomain() { // Init plugin without GoogleService-Info.plist. - plugin = FLTGoogleSignInPlugin( - signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: googleServiceInfo) + let plugin = createTestPlugin(googleServiceProperties: loadGoogleServiceInfo()) let params = FSIPlatformConfigurationParams.make( withClientId: nil, serverClientId: "mockServerClientId", @@ -318,17 +300,18 @@ class FLTGoogleSignInPluginTest: XCTestCase { var error: FlutterError? plugin.configure(withParameters: params, error: &error) - XCTAssertNil(error) - XCTAssertNil(fakeSignIn.configuration?.hostedDomain) + #expect(error == nil) + #expect(fakeSignIn.configuration?.hostedDomain == nil) // Set in example app GoogleService-Info.plist. - XCTAssertEqual( - fakeSignIn.configuration?.clientID, - "479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com") + #expect( + fakeSignIn.configuration?.clientID + == "479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com") // Overridden by params. - XCTAssertEqual(fakeSignIn.configuration?.serverClientID, "mockServerClientId") + #expect(fakeSignIn.configuration?.serverClientID == "mockServerClientId") } - func testInitInfoPlist() { + @Test func initInfoPlist() { + let plugin = createTestPlugin() let params = FSIPlatformConfigurationParams.make( withClientId: nil, serverClientId: nil, @@ -336,60 +319,60 @@ class FLTGoogleSignInPluginTest: XCTestCase { var error: FlutterError? plugin.configure(withParameters: params, error: &error) - XCTAssertNil(error) + #expect(error == nil) // No configuration should be set, allowing the SDK to use its default behavior // (which is to load configuration information from Info.plist). - XCTAssertNil(fakeSignIn.configuration) + #expect(fakeSignIn.configuration == nil) } // MARK: - restorePreviousSignIn - func testSignInSilently() async { + @Test func signInSilently() async { + let plugin = createTestPlugin() let fakeUser = TestGoogleUser("mockID") - let fakeUserProfile = TestProfileData( - name: "mockDisplay", email: "mock@example.com", - imageURL: URL(string: "https://example.com/profile.png")) - fakeUser.profile = fakeUserProfile + let fakeUserProfile = TestProfileData( + name: "mockDisplay", email: "mock@example.com", + imageURL: URL(string: "https://example.com/profile.png")) + fakeUser.profile = fakeUserProfile fakeSignIn.user = fakeUser - let expectation = self.expectation(description: "completion called") - plugin.restorePreviousSignIn { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.error) - XCTAssertNotNil(result?.success) - let user = result?.success?.user - XCTAssertEqual(user?.displayName, fakeUserProfile.name) - XCTAssertEqual(user?.email, fakeUserProfile.email) - XCTAssertEqual(user?.userId, fakeUser.userID) - XCTAssertEqual(user?.photoUrl, fakeUserProfile.imageURL?.absoluteString) - XCTAssertEqual(result?.success?.accessToken, fakeUser.accessToken.tokenString) - XCTAssertNil(result?.success?.serverAuthCode) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.restorePreviousSignIn { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success != nil) + #expect(result?.success?.user.displayName == fakeUserProfile.name) + #expect(result?.success?.user.email == fakeUserProfile.email) + #expect(result?.success?.user.userId == fakeUser.userID) + #expect(result?.success?.user.photoUrl == fakeUserProfile.imageURL?.absoluteString) + #expect(result?.success?.accessToken == fakeUser.accessToken.tokenString) + #expect(result?.success?.serverAuthCode == nil) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRestorePreviousSignInWithError() async { + @Test func restorePreviousSignInWithError() async { + let plugin = createTestPlugin() let sdkError = NSError( domain: kGIDSignInErrorDomain, code: GIDSignInError.hasNoAuthInKeychain.rawValue, userInfo: nil) fakeSignIn.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.restorePreviousSignIn { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.success) - XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.noAuthInKeychain) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.restorePreviousSignIn { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == FSIGoogleSignInErrorCode.noAuthInKeychain) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - signIn - func testSignIn() async { - plugin = FLTGoogleSignInPlugin( - signIn: fakeSignIn, viewProvider: viewProvider, googleServiceProperties: googleServiceInfo) + @Test func signIn() async { + let plugin = createTestPlugin() let fakeUser = TestGoogleUser("mockID") let fakeUserProfile = TestProfileData( name: "mockDisplay", email: "mock@example.com", @@ -404,22 +387,22 @@ class FLTGoogleSignInPluginTest: XCTestCase { fakeSignIn.signInResult = fakeSignInResult - let expectation = self.expectation(description: "completion called") - plugin.signIn(withScopeHint: [], nonce: nil) { result, error in - XCTAssertNil(error) - let user = result?.success?.user - XCTAssertEqual(user?.displayName, "mockDisplay") - XCTAssertEqual(user?.email, "mock@example.com") - XCTAssertEqual(user?.userId, "mockID") - XCTAssertEqual(user?.photoUrl, "https://example.com/profile.png") - XCTAssertEqual(result?.success?.accessToken, accessToken) - XCTAssertEqual(result?.success?.serverAuthCode, serverAuthCode) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + #expect(error == nil) + #expect(result?.success?.user.displayName == "mockDisplay") + #expect(result?.success?.user.email == "mock@example.com") + #expect(result?.success?.user.userId == "mockID") + #expect(result?.success?.user.photoUrl == "https://example.com/profile.png") + #expect(result?.success?.accessToken == accessToken) + #expect(result?.success?.serverAuthCode == serverAuthCode) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testSignInWithScopeHint() async { + @Test func signInWithScopeHint() async { + let plugin = createTestPlugin() var initializationError: FlutterError? plugin.configure( withParameters: FSIPlatformConfigurationParams.make( @@ -427,7 +410,7 @@ class FLTGoogleSignInPluginTest: XCTestCase { serverClientId: nil, hostedDomain: nil), error: &initializationError) - XCTAssertNil(initializationError) + #expect(initializationError == nil) let fakeUser = TestGoogleUser("mockID") let fakeSignInResult = TestSignInResult(user: fakeUser) @@ -435,19 +418,20 @@ class FLTGoogleSignInPluginTest: XCTestCase { let requestedScopes = ["scope1", "scope2"] fakeSignIn.signInResult = fakeSignInResult - let expectation = self.expectation(description: "completion called") - plugin.signIn(withScopeHint: requestedScopes, nonce: nil) { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.error) - XCTAssertEqual(result?.success?.user.userId, "mockID") - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: requestedScopes, nonce: nil) { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success?.user.userId == "mockID") + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) - XCTAssertEqual(Set(fakeSignIn.additionalScopes ?? []), Set(requestedScopes)) + #expect(Set(fakeSignIn.additionalScopes ?? []) == Set(requestedScopes)) } - func testSignInWithNonce() async { + @Test func signInWithNonce() async { + let plugin = createTestPlugin() var initializationError: FlutterError? plugin.configure( withParameters: FSIPlatformConfigurationParams.make( @@ -455,7 +439,7 @@ class FLTGoogleSignInPluginTest: XCTestCase { serverClientId: nil, hostedDomain: nil), error: &initializationError) - XCTAssertNil(initializationError) + #expect(initializationError == nil) let fakeUser = TestGoogleUser("mockID") let fakeSignInResult = TestSignInResult(user: fakeUser) @@ -463,19 +447,20 @@ class FLTGoogleSignInPluginTest: XCTestCase { let nonce = "A nonce" fakeSignIn.signInResult = fakeSignInResult - let expectation = self.expectation(description: "completion called") - plugin.signIn(withScopeHint: [], nonce: nonce) { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.error) - XCTAssertEqual(result?.success?.user.userId, "mockID") - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nonce) { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success?.user.userId == "mockID") + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) - XCTAssertEqual(fakeSignIn.nonce, nonce) + #expect(fakeSignIn.nonce == nonce) } - func testSignInAlreadyGranted() async { + @Test func signInAlreadyGranted() async { + let plugin = createTestPlugin() let fakeUser = TestGoogleUser("mockID") let fakeSignInResult = TestSignInResult(user: fakeUser) @@ -486,155 +471,164 @@ class FLTGoogleSignInPluginTest: XCTestCase { userInfo: nil) fakeSignIn.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.signIn(withScopeHint: [], nonce: nil) { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.error) - XCTAssertEqual(result?.success?.user.userId, "mockID") - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success?.user.userId == "mockID") + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testSignInError() async { + @Test func signInError() async { + let plugin = createTestPlugin() let sdkError = NSError( domain: kGIDSignInErrorDomain, code: GIDSignInError.canceled.rawValue, userInfo: nil) fakeSignIn.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.signIn(withScopeHint: [], nonce: nil) { result, error in - // Known errors from the SDK are returned as structured data, not - // FlutterError. - XCTAssertNil(error) - XCTAssertNil(result?.success) - XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.canceled) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + // Known errors from the SDK are returned as structured data, not + // FlutterError. + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .canceled) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testSignInExceptionReturnsError() async { + @Test func signInExceptionReturnsError() async { + let plugin = createTestPlugin() fakeSignIn.exception = NSException( name: NSExceptionName(rawValue: "MockName"), reason: "MockReason", userInfo: nil) - let expectation = self.expectation(description: "completion called") - plugin.signIn(withScopeHint: [], nonce: nil) { result, error in - // Unexpected errors, such as runtime exceptions, are returned as - // FlutterError. - XCTAssertNil(result) - XCTAssertEqual(error?.code, "google_sign_in") - XCTAssertEqual(error?.message, "MockReason") - XCTAssertEqual(error?.details as! String, "MockName") - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + // Unexpected errors, such as runtime exceptions, are returned as + // FlutterError. + #expect(result == nil) + #expect(error?.code == "google_sign_in") + #expect(error?.message == "MockReason") + #expect(error?.details as! String == "MockName") + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - refreshedAuthorizationTokens - func testRefreshTokens() async { - let fakeUser = addSignedInUser() + @Test func refreshTokens() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) // TestGoogleUser passes itself as the result's user property, so set the // fake result data on this object. fakeUser.idToken = TestToken("mockIdToken") fakeUser.accessToken = TestToken("mockAccessToken") - let expectation = self.expectation(description: "completion called") - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.error) - XCTAssertEqual(result?.success?.user.idToken, "mockIdToken") - XCTAssertEqual(result?.success?.accessToken, "mockAccessToken") - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success?.user.idToken == "mockIdToken") + #expect(result?.success?.accessToken == "mockAccessToken") + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensUnkownUser() async { - let expectation = self.expectation(description: "completion called") - plugin.refreshedAuthorizationTokens(forUser: "unknownUser") { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.success) - XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.userMismatch) - XCTAssertEqual(result?.error?.message, "The user is no longer signed in.") - expectation.fulfill() + @Test func refreshTokensUnkownUser() async { + let plugin = createTestPlugin() + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: "unknownUser") { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .userMismatch) + #expect(result?.error?.message == "The user is no longer signed in.") + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensNoAuthKeychainError() async { - let fakeUser = addSignedInUser() + @Test func refreshTokensNoAuthKeychainError() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) let sdkError = NSError( domain: kGIDSignInErrorDomain, code: GIDSignInError.hasNoAuthInKeychain.rawValue, userInfo: nil) fakeUser.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.success) - XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.noAuthInKeychain) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .noAuthInKeychain) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensCancelledError() async { - let fakeUser = addSignedInUser() + @Test func refreshTokensCancelledError() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) let sdkError = NSError( domain: kGIDSignInErrorDomain, code: GIDSignInError.canceled.rawValue, userInfo: nil) fakeUser.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.success) - XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.canceled) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .canceled) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensURLError() async { - let fakeUser = addSignedInUser() + @Test func refreshTokensURLError() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) let sdkError = NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil) fakeUser.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - XCTAssertNil(result?.error) - XCTAssertNil(result?.success) - let expectedCode = "\(NSURLErrorDomain): \(NSURLErrorTimedOut)" - XCTAssertEqual(error?.code, expectedCode) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + #expect(result?.error == nil) + #expect(result?.success == nil) + let expectedCode = "\(NSURLErrorDomain): \(NSURLErrorTimedOut)" + #expect(error?.code == expectedCode) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRefreshTokensUnknownError() async { - let fakeUser = addSignedInUser() + @Test func refreshTokensUnknownError() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) let sdkError = NSError(domain: "BogusDomain", code: 42, userInfo: nil) fakeUser.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - XCTAssertNil(result?.success) - XCTAssertEqual(error?.code, "BogusDomain: 42") - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + #expect(result?.success == nil) + #expect(error?.code == "BogusDomain: 42") + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - addScopes - func testRequestScopesPassesScopes() async { - let fakeUser = addSignedInUser() + @Test func requestScopesPassesScopes() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) // Create a different instance to return in the result, to avoid a retain cycle. let fakeResultUser = TestGoogleUser(fakeUser.userID!) let fakeSignInResult = TestSignInResult(user: fakeResultUser) @@ -642,100 +636,123 @@ class FLTGoogleSignInPluginTest: XCTestCase { let scopes = ["mockScope1"] - let expectation = self.expectation(description: "completion called") - plugin.addScopes(scopes, forUser: fakeUser.userID!) { result, error in - XCTAssertNil(error) - XCTAssertNotNil(result?.success) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.addScopes(scopes, forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.success != nil) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) - XCTAssertEqual(fakeUser.requestedScopes?.first, scopes.first) + #expect(fakeUser.requestedScopes?.first == scopes.first) } - func testRequestScopesResultErrorIfNotSignedIn() async { - let expectation = self.expectation(description: "completion called") - plugin.addScopes(["mockScope1"], forUser: "unknownUser") { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.success) - XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.userMismatch) - expectation.fulfill() + @Test func requestScopesResultErrorIfNotSignedIn() async { + let plugin = createTestPlugin() + await confirmation("completion called") { confirmed in + plugin.addScopes(["mockScope1"], forUser: "unknownUser") { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .userMismatch) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRequestScopesIfNoMissingScope() async { - let fakeUser = addSignedInUser() + @Test func requestScopesIfNoMissingScope() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) let sdkError = NSError( domain: kGIDSignInErrorDomain, code: GIDSignInError.scopesAlreadyGranted.rawValue, userInfo: nil) fakeUser.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.success) - XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.scopesAlreadyGranted) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .scopesAlreadyGranted) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRequestScopesResultErrorIfMismatchingUser() async { - let fakeUser = addSignedInUser() + @Test func requestScopesResultErrorIfMismatchingUser() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) let sdkError = NSError( domain: kGIDSignInErrorDomain, code: GIDSignInError.mismatchWithCurrentUser.rawValue, userInfo: nil) fakeUser.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in - XCTAssertNil(error) - XCTAssertNil(result?.success) - XCTAssertEqual(result?.error?.type, FSIGoogleSignInErrorCode.userMismatch) - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .userMismatch) + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRequestScopesWithUnknownError() async { - let fakeUser = addSignedInUser() + @Test func requestScopesWithUnknownError() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) let sdkError = NSError(domain: "BogusDomain", code: 42, userInfo: nil) fakeUser.error = sdkError - let expectation = self.expectation(description: "completion called") - plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in - XCTAssertNil(result) - XCTAssertEqual(error?.code, "BogusDomain: 42") - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in + #expect(result == nil) + #expect(error?.code == "BogusDomain: 42") + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } - func testRequestScopesException() async { - let fakeUser = addSignedInUser() + @Test func requestScopesException() async { + let plugin = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) fakeUser.exception = NSException( name: NSExceptionName(rawValue: "MockName"), reason: "MockReason", userInfo: nil) - let expectation = self.expectation(description: "completion called") - plugin.addScopes([], forUser: fakeUser.userID!) { result, error in - XCTAssertNil(result) - XCTAssertEqual(error?.code, "request_scopes") - XCTAssertEqual(error?.message, "MockReason") - XCTAssertEqual(error?.details as! String, "MockName") - expectation.fulfill() + await confirmation("completion called") { confirmed in + plugin.addScopes([], forUser: fakeUser.userID!) { result, error in + #expect(result == nil) + #expect(error?.code == "request_scopes") + #expect(error?.message == "MockReason") + #expect(error?.details as! String == "MockName") + confirmed() + } } - await fulfillment(of: [expectation], timeout: 5.0) } // MARK: - Utils - func addSignedInUser() -> TestGoogleUser { + func loadGoogleServiceInfo() -> [String: Any]? { + if let plistPath = Bundle(for: type(of: fakeSignIn)).path( + forResource: "GoogleService-Info", ofType: "plist") + { + return NSDictionary(contentsOfFile: plistPath) as? [String: Any] + } + return nil + } + + func createTestPlugin( + viewProvider: TestViewProvider = TestViewProvider(), + googleServiceProperties: [String: Any]? = nil + ) -> FLTGoogleSignInPlugin { + return FLTGoogleSignInPlugin( + signIn: fakeSignIn, viewProvider: viewProvider, + googleServiceProperties: googleServiceProperties) + } + + func addSignedInUser(to plugin: FLTGoogleSignInPlugin) -> TestGoogleUser { let identifier = "fakeID" let user = TestGoogleUser(identifier) plugin.usersByIdentifier[identifier] = user From 18fa05a066c25258a7cc89aced7b930c9a326bf8 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 13 Jan 2026 16:50:29 -0500 Subject: [PATCH 04/13] Gemini review --- .../darwin/Tests/GoogleSignInTests.swift | 24 ++++++++++++++----- .../macos/Runner.xcodeproj/project.pbxproj | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift index 59edf9a5af14..e2b66e619e2d 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -56,7 +56,11 @@ class TestSignIn: NSObject, FSIGIDSignIn { if let exception = exception { exception.raise() } - completion?(user, user != nil ? nil : error) + if let user { + completion?(user, nil) + } else { + completion?(nil, error) + } } func signOut() { @@ -85,7 +89,11 @@ class TestSignIn: NSObject, FSIGIDSignIn { self.hint = hint self.additionalScopes = additionalScopes self.nonce = nonce - completion?(signInResult, signInResult != nil ? nil : error) + if let signInResult { + completion?(signInResult, nil) + } else { + completion?(nil, error) + } } #else func signIn( @@ -102,7 +110,11 @@ class TestSignIn: NSObject, FSIGIDSignIn { self.hint = hint self.additionalScopes = additionalScopes self.nonce = nonce - completion?(signInResult, signInResult != nil ? nil : error) + if let signInResult { + completion?(signInResult, nil) + } else { + completion?(nil, error) + } } #endif } @@ -155,7 +167,7 @@ class TestGoogleUser: NSObject, FSIGIDGoogleUser { var userID: String? var profile: FSIGIDProfileData? var grantedScopes: [String]? - var accessToken: FSIGIDToken = TestToken("Acces") + var accessToken: FSIGIDToken = TestToken("Access") var refreshToken: FSIGIDToken = TestToken("Refresh") var idToken: FSIGIDToken? @@ -513,7 +525,7 @@ struct FLTGoogleSignInPluginTest { #expect(result == nil) #expect(error?.code == "google_sign_in") #expect(error?.message == "MockReason") - #expect(error?.details as! String == "MockName") + #expect(error?.details as? String == "MockName") confirmed() } } @@ -726,7 +738,7 @@ struct FLTGoogleSignInPluginTest { #expect(result == nil) #expect(error?.code == "request_scopes") #expect(error?.message == "MockReason") - #expect(error?.details as! String == "MockName") + #expect(error?.details as? String == "MockName") confirmed() } } diff --git a/packages/google_sign_in/google_sign_in_ios/example/macos/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in_ios/example/macos/Runner.xcodeproj/project.pbxproj index b24e24ef2b03..e19d4819efb0 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in_ios/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ From 258f7980c229518bb7349993e393b0d9840def11 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 13 Jan 2026 16:51:51 -0500 Subject: [PATCH 05/13] Update false_secrets path --- packages/google_sign_in/google_sign_in_ios/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml index d382b5147a2c..d49fa0964013 100644 --- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml @@ -40,7 +40,7 @@ topics: # The example deliberately includes limited-use secrets. false_secrets: - /darwin/Tests/GoogleService-Info.plist - - /darwin/Tests/GoogleSignInTests.m + - /darwin/Tests/GoogleSignInTests.swift - /example/ios/Runner/Info.plist - /example/lib/main.dart - /example/macos/Runner/Info.plist From d5dd70c67f753e0093df28d93390d371426c1672 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jan 2026 10:35:09 -0500 Subject: [PATCH 06/13] Parameterize tests --- .../darwin/Tests/GoogleSignInTests.swift | 89 ++++++------------- 1 file changed, 27 insertions(+), 62 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift index e2b66e619e2d..06a85ed40aa5 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -565,12 +565,18 @@ struct FLTGoogleSignInPluginTest { } } - @Test func refreshTokensNoAuthKeychainError() async { + @Test(arguments: [ + (GIDSignInError.hasNoAuthInKeychain.rawValue, FSIGoogleSignInErrorCode.noAuthInKeychain), + (GIDSignInError.canceled.rawValue, .canceled), + ]) func refreshTokensGIDSignInErrorDomainErrors( + signInSDKErrorCode: Int, + expectedPigeonErrorCode: FSIGoogleSignInErrorCode + ) async { let plugin = createTestPlugin() let fakeUser = addSignedInUser(to: plugin) let sdkError = NSError( - domain: kGIDSignInErrorDomain, code: GIDSignInError.hasNoAuthInKeychain.rawValue, + domain: kGIDSignInErrorDomain, code: signInSDKErrorCode, userInfo: nil) fakeUser.error = sdkError @@ -578,64 +584,36 @@ struct FLTGoogleSignInPluginTest { plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in #expect(error == nil) #expect(result?.success == nil) - #expect(result?.error?.type == .noAuthInKeychain) - confirmed() - } - } - } - - @Test func refreshTokensCancelledError() async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) - - let sdkError = NSError( - domain: kGIDSignInErrorDomain, code: GIDSignInError.canceled.rawValue, userInfo: nil) - fakeUser.error = sdkError - - await confirmation("completion called") { confirmed in - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - #expect(error == nil) - #expect(result?.success == nil) - #expect(result?.error?.type == .canceled) + #expect(result?.error?.type == expectedPigeonErrorCode) confirmed() } } } - @Test func refreshTokensURLError() async { + @Test(arguments: [ + (NSURLErrorDomain, NSURLErrorTimedOut), + ("BogusDomain", 42), + ]) func refreshTokensOtherDomainErrors( + errorDomain: String, + errorCode: Int + ) async { let plugin = createTestPlugin() let fakeUser = addSignedInUser(to: plugin) - let sdkError = NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil) + let sdkError = NSError(domain: errorDomain, code: errorCode, userInfo: nil) fakeUser.error = sdkError await confirmation("completion called") { confirmed in plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in #expect(result?.error == nil) #expect(result?.success == nil) - let expectedCode = "\(NSURLErrorDomain): \(NSURLErrorTimedOut)" + let expectedCode = "\(errorDomain): \(errorCode)" #expect(error?.code == expectedCode) confirmed() } } } - @Test func refreshTokensUnknownError() async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) - - let sdkError = NSError(domain: "BogusDomain", code: 42, userInfo: nil) - fakeUser.error = sdkError - - await confirmation("completion called") { confirmed in - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - #expect(result?.success == nil) - #expect(error?.code == "BogusDomain: 42") - confirmed() - } - } - } - // MARK: - addScopes @Test func requestScopesPassesScopes() async { @@ -670,12 +648,18 @@ struct FLTGoogleSignInPluginTest { } } - @Test func requestScopesIfNoMissingScope() async { + @Test(arguments: [ + (GIDSignInError.scopesAlreadyGranted.rawValue, FSIGoogleSignInErrorCode.scopesAlreadyGranted), + (GIDSignInError.mismatchWithCurrentUser.rawValue, FSIGoogleSignInErrorCode.userMismatch), + ]) func requestScopesGIDSignInErrorDomainErrors( + signInSDKErrorCode: Int, + expectedPigeonErrorCode: FSIGoogleSignInErrorCode + ) async { let plugin = createTestPlugin() let fakeUser = addSignedInUser(to: plugin) let sdkError = NSError( - domain: kGIDSignInErrorDomain, code: GIDSignInError.scopesAlreadyGranted.rawValue, + domain: kGIDSignInErrorDomain, code: signInSDKErrorCode, userInfo: nil) fakeUser.error = sdkError @@ -683,26 +667,7 @@ struct FLTGoogleSignInPluginTest { plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in #expect(error == nil) #expect(result?.success == nil) - #expect(result?.error?.type == .scopesAlreadyGranted) - confirmed() - } - } - } - - @Test func requestScopesResultErrorIfMismatchingUser() async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) - - let sdkError = NSError( - domain: kGIDSignInErrorDomain, code: GIDSignInError.mismatchWithCurrentUser.rawValue, - userInfo: nil) - fakeUser.error = sdkError - - await confirmation("completion called") { confirmed in - plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in - #expect(error == nil) - #expect(result?.success == nil) - #expect(result?.error?.type == .userMismatch) + #expect(result?.error?.type == expectedPigeonErrorCode) confirmed() } } From f8294c2685cf45e122be3be4f93655a56ed72967 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jan 2026 11:56:20 -0500 Subject: [PATCH 07/13] More parameters, add suites --- .../darwin/Tests/GoogleSignInTests.swift | 828 +++++++++--------- 1 file changed, 404 insertions(+), 424 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift index 06a85ed40aa5..65285c2973ef 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -228,12 +228,9 @@ class TestGoogleUser: NSObject, FSIGIDGoogleUser { #endif } -struct FLTGoogleSignInPluginTest { - // A fake that replaces the real global GoogleSignIn instance for tests. - var fakeSignIn = TestSignIn() - +struct GoogleSignInPluginTests { @Test func signOut() { - let plugin = createTestPlugin() + let (plugin, fakeSignIn) = createTestPlugin() var error: FlutterError? plugin.signOutWithError(&error) #expect(fakeSignIn.signOutCalled == true) @@ -241,7 +238,7 @@ struct FLTGoogleSignInPluginTest { } @Test func disconnect() async { - let plugin = createTestPlugin() + let (plugin, _) = createTestPlugin() await confirmation("expect result returns true") { confirmed in plugin.disconnect { error in #expect(error == nil) @@ -250,489 +247,472 @@ struct FLTGoogleSignInPluginTest { } } - // MARK: - Configure - - @Test func initNoClientIdNoError() { - // Init plugin without GoogleService-Info.plist. - let plugin = createTestPlugin(googleServiceProperties: nil) - - // init call does not provide a clientId. - let params = FSIPlatformConfigurationParams.make( - withClientId: nil, - serverClientId: nil, - hostedDomain: nil) - - var error: FlutterError? - plugin.configure(withParameters: params, error: &error) - #expect(error == nil) - } - - @Test func initGoogleServiceInfoPlist() { - let plugin = createTestPlugin(googleServiceProperties: loadGoogleServiceInfo()) - let params = FSIPlatformConfigurationParams.make( - withClientId: nil, - serverClientId: nil, - hostedDomain: "example.com") - - var error: FlutterError? - plugin.configure(withParameters: params, error: &error) - #expect(error == nil) - #expect(fakeSignIn.configuration?.hostedDomain == "example.com") - // Set in example app GoogleService-Info.plist. - #expect( - fakeSignIn.configuration?.clientID - == "479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com") - #expect(fakeSignIn.configuration?.serverClientID == "YOUR_SERVER_CLIENT_ID") - } - - @Test func initDynamicClientIdNullDomain() { - // Init plugin without GoogleService-Info.plist. - let plugin = createTestPlugin(googleServiceProperties: nil) - - let params = FSIPlatformConfigurationParams.make( - withClientId: "mockClientId", - serverClientId: nil, - hostedDomain: nil) - - var error: FlutterError? - plugin.configure(withParameters: params, error: &error) - #expect(error == nil) - #expect(fakeSignIn.configuration?.hostedDomain == nil) - #expect(fakeSignIn.configuration?.clientID == "mockClientId") - #expect(fakeSignIn.configuration?.serverClientID == nil) - } - - @Test func initDynamicServerClientIdNullDomain() { - // Init plugin without GoogleService-Info.plist. - let plugin = createTestPlugin(googleServiceProperties: loadGoogleServiceInfo()) - let params = FSIPlatformConfigurationParams.make( - withClientId: nil, - serverClientId: "mockServerClientId", - hostedDomain: nil) - - var error: FlutterError? - plugin.configure(withParameters: params, error: &error) - #expect(error == nil) - #expect(fakeSignIn.configuration?.hostedDomain == nil) - // Set in example app GoogleService-Info.plist. - #expect( - fakeSignIn.configuration?.clientID - == "479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com") - // Overridden by params. - #expect(fakeSignIn.configuration?.serverClientID == "mockServerClientId") - } - - @Test func initInfoPlist() { - let plugin = createTestPlugin() - let params = FSIPlatformConfigurationParams.make( - withClientId: nil, - serverClientId: nil, - hostedDomain: "example.com") - - var error: FlutterError? - plugin.configure(withParameters: params, error: &error) - #expect(error == nil) - // No configuration should be set, allowing the SDK to use its default behavior - // (which is to load configuration information from Info.plist). - #expect(fakeSignIn.configuration == nil) - } - - // MARK: - restorePreviousSignIn - - @Test func signInSilently() async { - let plugin = createTestPlugin() - let fakeUser = TestGoogleUser("mockID") - let fakeUserProfile = TestProfileData( - name: "mockDisplay", email: "mock@example.com", - imageURL: URL(string: "https://example.com/profile.png")) - fakeUser.profile = fakeUserProfile - fakeSignIn.user = fakeUser - - await confirmation("completion called") { confirmed in - plugin.restorePreviousSignIn { result, error in - #expect(error == nil) - #expect(result?.error == nil) - #expect(result?.success != nil) - #expect(result?.success?.user.displayName == fakeUserProfile.name) - #expect(result?.success?.user.email == fakeUserProfile.email) - #expect(result?.success?.user.userId == fakeUser.userID) - #expect(result?.success?.user.photoUrl == fakeUserProfile.imageURL?.absoluteString) - #expect(result?.success?.accessToken == fakeUser.accessToken.tokenString) - #expect(result?.success?.serverAuthCode == nil) - confirmed() - } + @Suite("configure") struct ConfigureTests { + @Test func configureFromAppInfoPlist() { + let (plugin, fakeSignIn) = createTestPlugin() + let params = FSIPlatformConfigurationParams.make( + withClientId: nil, + serverClientId: nil, + hostedDomain: "example.com") + + var error: FlutterError? + plugin.configure(withParameters: params, error: &error) + #expect(error == nil) + // No configuration should be set, allowing the SDK to use its default behavior + // (which is to load configuration information from the app's Info.plist). + #expect(fakeSignIn.configuration == nil) } - } - - @Test func restorePreviousSignInWithError() async { - let plugin = createTestPlugin() - let sdkError = NSError( - domain: kGIDSignInErrorDomain, code: GIDSignInError.hasNoAuthInKeychain.rawValue, - userInfo: nil) - fakeSignIn.error = sdkError - await confirmation("completion called") { confirmed in - plugin.restorePreviousSignIn { result, error in - #expect(error == nil) - #expect(result?.success == nil) - #expect(result?.error?.type == FSIGoogleSignInErrorCode.noAuthInKeychain) - confirmed() + @Test( + arguments: [ + // Use GoogleService-Info.plist, but add a domain. + (nil, nil, "example.com", true), + // Use GoogleService-Info.plist, but override the server client ID. + (nil, "overridingServerClientId", nil, true), + // No plist, providing only some values. + ("runtimeClientId", nil, nil, false), + ("runtimeClientId", "runtimeSeverClientId", nil, false), + ] as [(String?, String?, String?, Bool)]) func configureFromExplicitValues( + dynamicClientId: String?, + dynamicServerClientId: String?, + dynamicHostedDomain: String?, + useGoogleServiceInfoPlist: Bool + ) + { + let (plugin, fakeSignIn) = createTestPlugin( + googleServiceProperties: useGoogleServiceInfoPlist ? loadGoogleServiceInfo() : nil) + let params = FSIPlatformConfigurationParams.make( + withClientId: dynamicClientId, + serverClientId: dynamicServerClientId, + hostedDomain: dynamicHostedDomain) + + // Default configuration values are nil, or the values from GoogleService-Info.plist if + // that's being used. + var expectedClientId: String? = + useGoogleServiceInfoPlist + ? "479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com" : nil + var expectedServerClientId: String? = + useGoogleServiceInfoPlist ? "YOUR_SERVER_CLIENT_ID" : nil + var expectedDomain: String? = nil + // Any value passed in at runtime should override the default. + if let dynamicClientId { + expectedClientId = dynamicClientId + } + if let dynamicServerClientId { + expectedServerClientId = dynamicServerClientId + } + if let dynamicHostedDomain { + expectedDomain = dynamicHostedDomain } + + var error: FlutterError? + plugin.configure(withParameters: params, error: &error) + #expect(error == nil) + #expect( + fakeSignIn.configuration?.clientID + == expectedClientId) + #expect(fakeSignIn.configuration?.serverClientID == expectedServerClientId) + #expect(fakeSignIn.configuration?.hostedDomain == expectedDomain) } } - // MARK: - signIn - - @Test func signIn() async { - let plugin = createTestPlugin() - let fakeUser = TestGoogleUser("mockID") - let fakeUserProfile = TestProfileData( - name: "mockDisplay", email: "mock@example.com", - imageURL: URL(string: "https://example.com/profile.png")) - - let accessToken = "mockAccessToken" - let serverAuthCode = "mockAuthCode" - fakeUser.profile = fakeUserProfile - fakeUser.accessToken = TestToken(accessToken) - - let fakeSignInResult = TestSignInResult(user: fakeUser, serverAuthCode: serverAuthCode) - - fakeSignIn.signInResult = fakeSignInResult - - await confirmation("completion called") { confirmed in - plugin.signIn(withScopeHint: [], nonce: nil) { result, error in - #expect(error == nil) - #expect(result?.success?.user.displayName == "mockDisplay") - #expect(result?.success?.user.email == "mock@example.com") - #expect(result?.success?.user.userId == "mockID") - #expect(result?.success?.user.photoUrl == "https://example.com/profile.png") - #expect(result?.success?.accessToken == accessToken) - #expect(result?.success?.serverAuthCode == serverAuthCode) - confirmed() + @Suite("restorePreviousSignIn") struct RestorePreviousSignInTests { + @Test func restorePreviousSignInSuccess() async { + let (plugin, fakeSignIn) = createTestPlugin() + let fakeUser = TestGoogleUser("mockID") + let fakeUserProfile = TestProfileData( + name: "mockDisplay", email: "mock@example.com", + imageURL: URL(string: "https://example.com/profile.png")) + fakeUser.profile = fakeUserProfile + fakeSignIn.user = fakeUser + + await confirmation("completion called") { confirmed in + plugin.restorePreviousSignIn { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success != nil) + #expect(result?.success?.user.displayName == fakeUserProfile.name) + #expect(result?.success?.user.email == fakeUserProfile.email) + #expect(result?.success?.user.userId == fakeUser.userID) + #expect(result?.success?.user.photoUrl == fakeUserProfile.imageURL?.absoluteString) + #expect(result?.success?.accessToken == fakeUser.accessToken.tokenString) + #expect(result?.success?.serverAuthCode == nil) + confirmed() + } } } - } - - @Test func signInWithScopeHint() async { - let plugin = createTestPlugin() - var initializationError: FlutterError? - plugin.configure( - withParameters: FSIPlatformConfigurationParams.make( - withClientId: nil, - serverClientId: nil, - hostedDomain: nil), - error: &initializationError) - #expect(initializationError == nil) - - let fakeUser = TestGoogleUser("mockID") - let fakeSignInResult = TestSignInResult(user: fakeUser) - - let requestedScopes = ["scope1", "scope2"] - fakeSignIn.signInResult = fakeSignInResult - await confirmation("completion called") { confirmed in - plugin.signIn(withScopeHint: requestedScopes, nonce: nil) { result, error in - #expect(error == nil) - #expect(result?.error == nil) - #expect(result?.success?.user.userId == "mockID") - confirmed() + @Test func restorePreviousSignInError() async { + let (plugin, fakeSignIn) = createTestPlugin() + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.hasNoAuthInKeychain.rawValue, + userInfo: nil) + fakeSignIn.error = sdkError + + await confirmation("completion called") { confirmed in + plugin.restorePreviousSignIn { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == FSIGoogleSignInErrorCode.noAuthInKeychain) + confirmed() + } } } - - #expect(Set(fakeSignIn.additionalScopes ?? []) == Set(requestedScopes)) } - @Test func signInWithNonce() async { - let plugin = createTestPlugin() - var initializationError: FlutterError? - plugin.configure( - withParameters: FSIPlatformConfigurationParams.make( - withClientId: nil, - serverClientId: nil, - hostedDomain: nil), - error: &initializationError) - #expect(initializationError == nil) - - let fakeUser = TestGoogleUser("mockID") - let fakeSignInResult = TestSignInResult(user: fakeUser) - - let nonce = "A nonce" - fakeSignIn.signInResult = fakeSignInResult - - await confirmation("completion called") { confirmed in - plugin.signIn(withScopeHint: [], nonce: nonce) { result, error in - #expect(error == nil) - #expect(result?.error == nil) - #expect(result?.success?.user.userId == "mockID") - confirmed() + @Suite("signIn") struct SignInTests { + @Test func signInWithoutParameters() async { + let (plugin, fakeSignIn) = createTestPlugin() + let fakeUser = TestGoogleUser("mockID") + let fakeUserProfile = TestProfileData( + name: "mockDisplay", email: "mock@example.com", + imageURL: URL(string: "https://example.com/profile.png")) + + let accessToken = "mockAccessToken" + let serverAuthCode = "mockAuthCode" + fakeUser.profile = fakeUserProfile + fakeUser.accessToken = TestToken(accessToken) + + let fakeSignInResult = TestSignInResult(user: fakeUser, serverAuthCode: serverAuthCode) + + fakeSignIn.signInResult = fakeSignInResult + + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + #expect(error == nil) + #expect(result?.success?.user.displayName == "mockDisplay") + #expect(result?.success?.user.email == "mock@example.com") + #expect(result?.success?.user.userId == "mockID") + #expect(result?.success?.user.photoUrl == "https://example.com/profile.png") + #expect(result?.success?.accessToken == accessToken) + #expect(result?.success?.serverAuthCode == serverAuthCode) + confirmed() + } } } - #expect(fakeSignIn.nonce == nonce) - } + @Test func signInWithScopeHint() async { + let (plugin, fakeSignIn) = createTestPlugin() + var initializationError: FlutterError? + plugin.configure( + withParameters: FSIPlatformConfigurationParams.make( + withClientId: nil, + serverClientId: nil, + hostedDomain: nil), + error: &initializationError) + #expect(initializationError == nil) + + let fakeUser = TestGoogleUser("mockID") + let fakeSignInResult = TestSignInResult(user: fakeUser) + + let requestedScopes = ["scope1", "scope2"] + fakeSignIn.signInResult = fakeSignInResult + + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: requestedScopes, nonce: nil) { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success?.user.userId == "mockID") + confirmed() + } + } - @Test func signInAlreadyGranted() async { - let plugin = createTestPlugin() - let fakeUser = TestGoogleUser("mockID") - let fakeSignInResult = TestSignInResult(user: fakeUser) + #expect(Set(fakeSignIn.additionalScopes ?? []) == Set(requestedScopes)) + } - fakeSignIn.signInResult = fakeSignInResult + @Test func signInWithNonce() async { + let (plugin, fakeSignIn) = createTestPlugin() + var initializationError: FlutterError? + plugin.configure( + withParameters: FSIPlatformConfigurationParams.make( + withClientId: nil, + serverClientId: nil, + hostedDomain: nil), + error: &initializationError) + #expect(initializationError == nil) + + let fakeUser = TestGoogleUser("mockID") + let fakeSignInResult = TestSignInResult(user: fakeUser) + + let nonce = "A nonce" + fakeSignIn.signInResult = fakeSignInResult + + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nonce) { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success?.user.userId == "mockID") + confirmed() + } + } - let sdkError = NSError( - domain: kGIDSignInErrorDomain, code: GIDSignInError.scopesAlreadyGranted.rawValue, - userInfo: nil) - fakeSignIn.error = sdkError + #expect(fakeSignIn.nonce == nonce) + } - await confirmation("completion called") { confirmed in - plugin.signIn(withScopeHint: [], nonce: nil) { result, error in - #expect(error == nil) - #expect(result?.error == nil) - #expect(result?.success?.user.userId == "mockID") - confirmed() + @Test func signInAlreadyGranted() async { + let (plugin, fakeSignIn) = createTestPlugin() + let fakeUser = TestGoogleUser("mockID") + let fakeSignInResult = TestSignInResult(user: fakeUser) + + fakeSignIn.signInResult = fakeSignInResult + + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.scopesAlreadyGranted.rawValue, + userInfo: nil) + fakeSignIn.error = sdkError + + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success?.user.userId == "mockID") + confirmed() + } } } - } - @Test func signInError() async { - let plugin = createTestPlugin() - let sdkError = NSError( - domain: kGIDSignInErrorDomain, code: GIDSignInError.canceled.rawValue, userInfo: nil) - fakeSignIn.error = sdkError - - await confirmation("completion called") { confirmed in - plugin.signIn(withScopeHint: [], nonce: nil) { result, error in - // Known errors from the SDK are returned as structured data, not - // FlutterError. - #expect(error == nil) - #expect(result?.success == nil) - #expect(result?.error?.type == .canceled) - confirmed() + @Test func signInCanceled() async { + let (plugin, fakeSignIn) = createTestPlugin() + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: GIDSignInError.canceled.rawValue, userInfo: nil) + fakeSignIn.error = sdkError + + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + // Known errors from the SDK are returned as structured data, not + // FlutterError. + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .canceled) + confirmed() + } } } - } - @Test func signInExceptionReturnsError() async { - let plugin = createTestPlugin() - fakeSignIn.exception = NSException( - name: NSExceptionName(rawValue: "MockName"), - reason: "MockReason", - userInfo: nil) - - await confirmation("completion called") { confirmed in - plugin.signIn(withScopeHint: [], nonce: nil) { result, error in - // Unexpected errors, such as runtime exceptions, are returned as - // FlutterError. - #expect(result == nil) - #expect(error?.code == "google_sign_in") - #expect(error?.message == "MockReason") - #expect(error?.details as? String == "MockName") - confirmed() + @Test func signInExceptionReturnsError() async { + let (plugin, fakeSignIn) = createTestPlugin() + fakeSignIn.exception = NSException( + name: NSExceptionName(rawValue: "MockName"), + reason: "MockReason", + userInfo: nil) + + await confirmation("completion called") { confirmed in + plugin.signIn(withScopeHint: [], nonce: nil) { result, error in + // Unexpected errors, such as runtime exceptions, are returned as + // FlutterError. + #expect(result == nil) + #expect(error?.code == "google_sign_in") + #expect(error?.message == "MockReason") + #expect(error?.details as? String == "MockName") + confirmed() + } } } } - // MARK: - refreshedAuthorizationTokens - - @Test func refreshTokens() async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) - // TestGoogleUser passes itself as the result's user property, so set the - // fake result data on this object. - fakeUser.idToken = TestToken("mockIdToken") - fakeUser.accessToken = TestToken("mockAccessToken") - - await confirmation("completion called") { confirmed in - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - #expect(error == nil) - #expect(result?.error == nil) - #expect(result?.success?.user.idToken == "mockIdToken") - #expect(result?.success?.accessToken == "mockAccessToken") - confirmed() + @Suite("refreshedAuthorizationTokens") struct RefreshTests { + @Test func refreshTokensSuccess() async { + let (plugin, _) = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) + // TestGoogleUser passes itself as the result's user property, so set the + // fake result data on this object. + fakeUser.idToken = TestToken("mockIdToken") + fakeUser.accessToken = TestToken("mockAccessToken") + + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.error == nil) + #expect(result?.success?.user.idToken == "mockIdToken") + #expect(result?.success?.accessToken == "mockAccessToken") + confirmed() + } } } - } - @Test func refreshTokensUnkownUser() async { - let plugin = createTestPlugin() - await confirmation("completion called") { confirmed in - plugin.refreshedAuthorizationTokens(forUser: "unknownUser") { result, error in - #expect(error == nil) - #expect(result?.success == nil) - #expect(result?.error?.type == .userMismatch) - #expect(result?.error?.message == "The user is no longer signed in.") - confirmed() + @Test func refreshTokensUnkownUser() async { + let (plugin, _) = createTestPlugin() + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: "unknownUser") { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .userMismatch) + #expect(result?.error?.message == "The user is no longer signed in.") + confirmed() + } } } - } - @Test(arguments: [ - (GIDSignInError.hasNoAuthInKeychain.rawValue, FSIGoogleSignInErrorCode.noAuthInKeychain), - (GIDSignInError.canceled.rawValue, .canceled), - ]) func refreshTokensGIDSignInErrorDomainErrors( - signInSDKErrorCode: Int, - expectedPigeonErrorCode: FSIGoogleSignInErrorCode - ) async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) - - let sdkError = NSError( - domain: kGIDSignInErrorDomain, code: signInSDKErrorCode, - userInfo: nil) - fakeUser.error = sdkError - - await confirmation("completion called") { confirmed in - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - #expect(error == nil) - #expect(result?.success == nil) - #expect(result?.error?.type == expectedPigeonErrorCode) - confirmed() + @Test(arguments: [ + (GIDSignInError.hasNoAuthInKeychain.rawValue, FSIGoogleSignInErrorCode.noAuthInKeychain), + (GIDSignInError.canceled.rawValue, .canceled), + ]) func refreshTokensGIDSignInErrorDomainErrors( + signInSDKErrorCode: Int, + expectedPigeonErrorCode: FSIGoogleSignInErrorCode + ) async { + let (plugin, _) = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) + + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: signInSDKErrorCode, + userInfo: nil) + fakeUser.error = sdkError + + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == expectedPigeonErrorCode) + confirmed() + } } } - } - @Test(arguments: [ - (NSURLErrorDomain, NSURLErrorTimedOut), - ("BogusDomain", 42), - ]) func refreshTokensOtherDomainErrors( - errorDomain: String, - errorCode: Int - ) async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) - - let sdkError = NSError(domain: errorDomain, code: errorCode, userInfo: nil) - fakeUser.error = sdkError - - await confirmation("completion called") { confirmed in - plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in - #expect(result?.error == nil) - #expect(result?.success == nil) - let expectedCode = "\(errorDomain): \(errorCode)" - #expect(error?.code == expectedCode) - confirmed() + @Test(arguments: [ + (NSURLErrorDomain, NSURLErrorTimedOut), + ("BogusDomain", 42), + ]) func refreshTokensOtherDomainErrors( + errorDomain: String, + errorCode: Int + ) async { + let (plugin, _) = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) + + let sdkError = NSError(domain: errorDomain, code: errorCode, userInfo: nil) + fakeUser.error = sdkError + + await confirmation("completion called") { confirmed in + plugin.refreshedAuthorizationTokens(forUser: fakeUser.userID!) { result, error in + #expect(result?.error == nil) + #expect(result?.success == nil) + let expectedCode = "\(errorDomain): \(errorCode)" + #expect(error?.code == expectedCode) + confirmed() + } } } } - // MARK: - addScopes + @Suite("addScopes") struct AddScopesTests { + @Test func addScopesPassesScopes() async { + let (plugin, _) = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) + // Create a different instance to return in the result, to avoid a retain cycle. + let fakeResultUser = TestGoogleUser(fakeUser.userID!) + let fakeSignInResult = TestSignInResult(user: fakeResultUser) + fakeUser.result = fakeSignInResult - @Test func requestScopesPassesScopes() async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) - // Create a different instance to return in the result, to avoid a retain cycle. - let fakeResultUser = TestGoogleUser(fakeUser.userID!) - let fakeSignInResult = TestSignInResult(user: fakeResultUser) - fakeUser.result = fakeSignInResult + let scopes = ["mockScope1"] - let scopes = ["mockScope1"] - - await confirmation("completion called") { confirmed in - plugin.addScopes(scopes, forUser: fakeUser.userID!) { result, error in - #expect(error == nil) - #expect(result?.success != nil) - confirmed() + await confirmation("completion called") { confirmed in + plugin.addScopes(scopes, forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.success != nil) + confirmed() + } } + #expect(fakeUser.requestedScopes?.first == scopes.first) } - #expect(fakeUser.requestedScopes?.first == scopes.first) - } - @Test func requestScopesResultErrorIfNotSignedIn() async { - let plugin = createTestPlugin() - await confirmation("completion called") { confirmed in - plugin.addScopes(["mockScope1"], forUser: "unknownUser") { result, error in - #expect(error == nil) - #expect(result?.success == nil) - #expect(result?.error?.type == .userMismatch) - confirmed() + @Test func addScopesErrorsIfNotSignedIn() async { + let (plugin, _) = createTestPlugin() + await confirmation("completion called") { confirmed in + plugin.addScopes(["mockScope1"], forUser: "unknownUser") { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == .userMismatch) + confirmed() + } } } - } - @Test(arguments: [ - (GIDSignInError.scopesAlreadyGranted.rawValue, FSIGoogleSignInErrorCode.scopesAlreadyGranted), - (GIDSignInError.mismatchWithCurrentUser.rawValue, FSIGoogleSignInErrorCode.userMismatch), - ]) func requestScopesGIDSignInErrorDomainErrors( - signInSDKErrorCode: Int, - expectedPigeonErrorCode: FSIGoogleSignInErrorCode - ) async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) - - let sdkError = NSError( - domain: kGIDSignInErrorDomain, code: signInSDKErrorCode, - userInfo: nil) - fakeUser.error = sdkError - - await confirmation("completion called") { confirmed in - plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in - #expect(error == nil) - #expect(result?.success == nil) - #expect(result?.error?.type == expectedPigeonErrorCode) - confirmed() + @Test(arguments: [ + (GIDSignInError.scopesAlreadyGranted.rawValue, FSIGoogleSignInErrorCode.scopesAlreadyGranted), + (GIDSignInError.mismatchWithCurrentUser.rawValue, FSIGoogleSignInErrorCode.userMismatch), + ]) func addScopesGIDSignInErrorDomainErrors( + signInSDKErrorCode: Int, + expectedPigeonErrorCode: FSIGoogleSignInErrorCode + ) async { + let (plugin, _) = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) + + let sdkError = NSError( + domain: kGIDSignInErrorDomain, code: signInSDKErrorCode, + userInfo: nil) + fakeUser.error = sdkError + + await confirmation("completion called") { confirmed in + plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in + #expect(error == nil) + #expect(result?.success == nil) + #expect(result?.error?.type == expectedPigeonErrorCode) + confirmed() + } } } - } - @Test func requestScopesWithUnknownError() async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) + @Test func addScopesUnknownError() async { + let (plugin, _) = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) - let sdkError = NSError(domain: "BogusDomain", code: 42, userInfo: nil) - fakeUser.error = sdkError + let sdkError = NSError(domain: "BogusDomain", code: 42, userInfo: nil) + fakeUser.error = sdkError - await confirmation("completion called") { confirmed in - plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in - #expect(result == nil) - #expect(error?.code == "BogusDomain: 42") - confirmed() + await confirmation("completion called") { confirmed in + plugin.addScopes(["mockScope1"], forUser: fakeUser.userID!) { result, error in + #expect(result == nil) + #expect(error?.code == "BogusDomain: 42") + confirmed() + } } } - } - @Test func requestScopesException() async { - let plugin = createTestPlugin() - let fakeUser = addSignedInUser(to: plugin) - - fakeUser.exception = NSException( - name: NSExceptionName(rawValue: "MockName"), - reason: "MockReason", - userInfo: nil) - - await confirmation("completion called") { confirmed in - plugin.addScopes([], forUser: fakeUser.userID!) { result, error in - #expect(result == nil) - #expect(error?.code == "request_scopes") - #expect(error?.message == "MockReason") - #expect(error?.details as? String == "MockName") - confirmed() + @Test func addScopesException() async { + let (plugin, _) = createTestPlugin() + let fakeUser = addSignedInUser(to: plugin) + + fakeUser.exception = NSException( + name: NSExceptionName(rawValue: "MockName"), + reason: "MockReason", + userInfo: nil) + + await confirmation("completion called") { confirmed in + plugin.addScopes([], forUser: fakeUser.userID!) { result, error in + #expect(result == nil) + #expect(error?.code == "request_scopes") + #expect(error?.message == "MockReason") + #expect(error?.details as? String == "MockName") + confirmed() + } } } } +} - // MARK: - Utils - - func loadGoogleServiceInfo() -> [String: Any]? { - if let plistPath = Bundle(for: type(of: fakeSignIn)).path( - forResource: "GoogleService-Info", ofType: "plist") - { - return NSDictionary(contentsOfFile: plistPath) as? [String: Any] - } - return nil +func loadGoogleServiceInfo() -> [String: Any]? { + if let plistPath = Bundle(for: TestSignIn.self).path( + forResource: "GoogleService-Info", ofType: "plist") + { + return NSDictionary(contentsOfFile: plistPath) as? [String: Any] } + return nil +} - func createTestPlugin( - viewProvider: TestViewProvider = TestViewProvider(), - googleServiceProperties: [String: Any]? = nil - ) -> FLTGoogleSignInPlugin { - return FLTGoogleSignInPlugin( +func createTestPlugin( + viewProvider: TestViewProvider = TestViewProvider(), + googleServiceProperties: [String: Any]? = nil +) -> (FLTGoogleSignInPlugin, TestSignIn) { + let fakeSignIn = TestSignIn() + return ( + FLTGoogleSignInPlugin( signIn: fakeSignIn, viewProvider: viewProvider, - googleServiceProperties: googleServiceProperties) - } + googleServiceProperties: googleServiceProperties), fakeSignIn + ) +} - func addSignedInUser(to plugin: FLTGoogleSignInPlugin) -> TestGoogleUser { - let identifier = "fakeID" - let user = TestGoogleUser(identifier) - plugin.usersByIdentifier[identifier] = user - return user - } +func addSignedInUser(to plugin: FLTGoogleSignInPlugin) -> TestGoogleUser { + let identifier = "fakeID" + let user = TestGoogleUser(identifier) + plugin.usersByIdentifier[identifier] = user + return user } From 93883bd07a2799fa40e2d23b68a0ac33b53ae9e3 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jan 2026 12:12:55 -0500 Subject: [PATCH 08/13] Fix sendability warnings --- .../darwin/Tests/GoogleSignInTests.swift | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift index 65285c2973ef..2376dc95afc0 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -142,12 +142,13 @@ class TestProfileData: NSObject, FSIGIDProfileData { } // Test implementation of FSIGIDToken. -class TestToken: NSObject, FSIGIDToken { - var tokenString: String - var expirationDate: Date? +final class TestToken: NSObject, FSIGIDToken { + let tokenString: String + let expirationDate: Date? - init(_ token: String) { + init(_ token: String, expiration: Date? = nil) { tokenString = token + expirationDate = expiration } } @@ -319,11 +320,15 @@ struct GoogleSignInPluginTests { @Suite("restorePreviousSignIn") struct RestorePreviousSignInTests { @Test func restorePreviousSignInSuccess() async { let (plugin, fakeSignIn) = createTestPlugin() - let fakeUser = TestGoogleUser("mockID") - let fakeUserProfile = TestProfileData( - name: "mockDisplay", email: "mock@example.com", - imageURL: URL(string: "https://example.com/profile.png")) - fakeUser.profile = fakeUserProfile + let userID = "mockID" + let fakeUser = TestGoogleUser(userID) + let accessToken = fakeUser.accessToken.tokenString + let name = "mockDislayName" + let email = "mock@example.com" + let imageURLString = "https://example.com/profile.png" + fakeUser.profile = TestProfileData( + name: name, email: email, + imageURL: URL(string: imageURLString)) fakeSignIn.user = fakeUser await confirmation("completion called") { confirmed in @@ -331,11 +336,11 @@ struct GoogleSignInPluginTests { #expect(error == nil) #expect(result?.error == nil) #expect(result?.success != nil) - #expect(result?.success?.user.displayName == fakeUserProfile.name) - #expect(result?.success?.user.email == fakeUserProfile.email) - #expect(result?.success?.user.userId == fakeUser.userID) - #expect(result?.success?.user.photoUrl == fakeUserProfile.imageURL?.absoluteString) - #expect(result?.success?.accessToken == fakeUser.accessToken.tokenString) + #expect(result?.success?.user.displayName == name) + #expect(result?.success?.user.email == email) + #expect(result?.success?.user.userId == userID) + #expect(result?.success?.user.photoUrl == imageURLString) + #expect(result?.success?.accessToken == accessToken) #expect(result?.success?.serverAuthCode == nil) confirmed() } From d106cda52797a9b61a7cd17e970b36c2136cc3eb Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jan 2026 12:22:03 -0500 Subject: [PATCH 09/13] Add missing @testable --- .../google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift index 2376dc95afc0..46d2f0a765cc 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -4,7 +4,7 @@ import GoogleSignIn import Testing -import google_sign_in_ios +@testable import google_sign_in_ios #if os(OSX) import FlutterMacOS From 2e653e63831a1d2c3747ea9c52898f471ae3bb54 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jan 2026 14:39:36 -0500 Subject: [PATCH 10/13] Existentialism --- .../darwin/Tests/GoogleSignInTests.swift | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift index 46d2f0a765cc..d83753726cf0 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.swift @@ -4,6 +4,7 @@ import GoogleSignIn import Testing + @testable import google_sign_in_ios #if os(OSX) @@ -31,9 +32,9 @@ class TestSignIn: NSObject, FSIGIDSignIn { var exception: NSException? // Results to use in completion callbacks. - var user: FSIGIDGoogleUser? + var user: (any FSIGIDGoogleUser)? var error: Error? - var signInResult: FSIGIDSignInResult? + var signInResult: (any FSIGIDSignInResult)? // Passed parameters. var hint: String? @@ -52,7 +53,7 @@ class TestSignIn: NSObject, FSIGIDSignIn { return true } - func restorePreviousSignIn(completion: ((FSIGIDGoogleUser?, Error?) -> Void)?) { + func restorePreviousSignIn(completion: (((any FSIGIDGoogleUser)?, Error?) -> Void)?) { if let exception = exception { exception.raise() } @@ -101,7 +102,7 @@ class TestSignIn: NSObject, FSIGIDSignIn { hint: String?, additionalScopes: [String]?, nonce: String?, - completion: ((FSIGIDSignInResult?, Error?) -> Void)? + completion: (((any FSIGIDSignInResult)?, Error?) -> Void)? ) { if let exception = exception { exception.raise() @@ -154,10 +155,10 @@ final class TestToken: NSObject, FSIGIDToken { // Test implementation of FSIGIDSignInResult. class TestSignInResult: NSObject, FSIGIDSignInResult { - var user: FSIGIDGoogleUser + var user: any FSIGIDGoogleUser var serverAuthCode: String? - init(user: FSIGIDGoogleUser, serverAuthCode: String? = nil) { + init(user: any FSIGIDGoogleUser, serverAuthCode: String? = nil) { self.user = user self.serverAuthCode = serverAuthCode } @@ -166,17 +167,17 @@ class TestSignInResult: NSObject, FSIGIDSignInResult { // Test implementation of FSIGIDGoogleUser. class TestGoogleUser: NSObject, FSIGIDGoogleUser { var userID: String? - var profile: FSIGIDProfileData? + var profile: (any FSIGIDProfileData)? var grantedScopes: [String]? - var accessToken: FSIGIDToken = TestToken("Access") - var refreshToken: FSIGIDToken = TestToken("Refresh") - var idToken: FSIGIDToken? + var accessToken: any FSIGIDToken = TestToken("Access") + var refreshToken: any FSIGIDToken = TestToken("Refresh") + var idToken: (any FSIGIDToken)? // An exception to throw from methods. var exception: NSException? // The result to return from addScopes:presentingViewController:completion:. - var result: FSIGIDSignInResult? + var result: (any FSIGIDSignInResult)? // The error to return from methods. var error: Error? @@ -193,7 +194,7 @@ class TestGoogleUser: NSObject, FSIGIDGoogleUser { userID = userIdentifier } - func refreshTokensIfNeeded(completion: @escaping (FSIGIDGoogleUser?, Error?) -> Void) { + func refreshTokensIfNeeded(completion: @escaping ((any FSIGIDGoogleUser)?, Error?) -> Void) { if let exception = exception { exception.raise() } @@ -204,7 +205,7 @@ class TestGoogleUser: NSObject, FSIGIDGoogleUser { func addScopes( _ scopes: [String], presenting presentingViewController: UIViewController, - completion: ((FSIGIDSignInResult?, Error?) -> Void)? + completion: (((any FSIGIDSignInResult)?, Error?) -> Void)? ) { self.requestedScopes = scopes self.presentingViewController = presentingViewController @@ -217,7 +218,7 @@ class TestGoogleUser: NSObject, FSIGIDGoogleUser { func addScopes( _ scopes: [String], presenting presentingWindow: NSWindow, - completion: ((FSIGIDSignInResult?, Error?) -> Void)? + completion: (((any FSIGIDSignInResult)?, Error?) -> Void)? ) { self.requestedScopes = scopes self.presentingWindow = presentingWindow From b05c0c285a3625b697255f9e207d5be6c4282ecd Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jan 2026 15:47:21 -0500 Subject: [PATCH 11/13] Remove modulemap --- .../darwin/google_sign_in_ios.podspec | 1 - .../darwin/google_sign_in_ios/Package.swift | 3 --- .../include/FLTGoogleSignInPlugin.modulemap | 10 ---------- .../include/google_sign_in_ios-umbrella.h | 10 ---------- 4 files changed, 24 deletions(-) delete mode 100644 packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/FLTGoogleSignInPlugin.modulemap delete mode 100644 packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios-umbrella.h diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios.podspec b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios.podspec index 1c236caaa292..9adb77fbe6a5 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios.podspec +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios.podspec @@ -14,7 +14,6 @@ Enables Google Sign-In in Flutter apps. s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_ios' } s.source_files = 'google_sign_in_ios/Sources/google_sign_in_ios/**/*.{h,m}' s.public_header_files = 'google_sign_in_ios/Sources/google_sign_in_ios/include/**/*.h' - s.module_map = 'google_sign_in_ios/Sources/google_sign_in_ios/include/FLTGoogleSignInPlugin.modulemap' # GTMSessionFetcher is a GoogleSignIn transitive dependency, added here as a # direct dependency to ensure a version which defines modules. diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Package.swift b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Package.swift index 10e0d4ca6c90..7b37176cecb5 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Package.swift +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Package.swift @@ -26,9 +26,6 @@ let package = Package( dependencies: [ .product(name: "GoogleSignIn", package: "GoogleSignIn-iOS") ], - exclude: [ - "include/google_sign_in_ios-umbrella.h", "include/FLTGoogleSignInPlugin.modulemap", - ], resources: [ .process("Resources") ], diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/FLTGoogleSignInPlugin.modulemap b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/FLTGoogleSignInPlugin.modulemap deleted file mode 100644 index 31e30d93c582..000000000000 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/FLTGoogleSignInPlugin.modulemap +++ /dev/null @@ -1,10 +0,0 @@ -framework module google_sign_in_ios { - umbrella header "google_sign_in_ios-umbrella.h" - - export * - module * { export * } - - explicit module Test { - header "FLTGoogleSignInPlugin_Test.h" - } -} diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios-umbrella.h b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios-umbrella.h deleted file mode 100644 index 4e914272c0e2..000000000000 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios-umbrella.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import -#import -#import - -FOUNDATION_EXPORT double google_sign_inVersionNumber; -FOUNDATION_EXPORT const unsigned char google_sign_inVersionString[]; From 872615748411d0659c8451a2446b37b94285db6b Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jan 2026 15:47:32 -0500 Subject: [PATCH 12/13] Version bump --- packages/google_sign_in/google_sign_in_ios/CHANGELOG.md | 4 ++++ packages/google_sign_in/google_sign_in_ios/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md index ae8b554ddeb8..9a8fa677fe0f 100644 --- a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.2.5 + +* Updates native header visibility for Swift testing. + ## 6.2.4 * Updates to Pigeon 26. diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml index d49fa0964013..0cf613cce834 100644 --- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: google_sign_in_ios description: iOS implementation of the google_sign_in plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 6.2.4 +version: 6.2.5 environment: sdk: ^3.9.0 From 468d35a7672a69f0ac07736b29a0dd78aaf2b943 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jan 2026 16:57:24 -0500 Subject: [PATCH 13/13] Add missing import so that target ifdefs work --- .../include/google_sign_in_ios/FLTGoogleSignInPlugin.h | 2 ++ .../include/google_sign_in_ios/FLTGoogleSignInPlugin_Test.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios/FLTGoogleSignInPlugin.h b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios/FLTGoogleSignInPlugin.h index 2044ead34e3e..9e74136a26f5 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios/FLTGoogleSignInPlugin.h +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios/FLTGoogleSignInPlugin.h @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +@import Foundation; + #if TARGET_OS_OSX #import #else diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios/FLTGoogleSignInPlugin_Test.h b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios/FLTGoogleSignInPlugin_Test.h index 7e0f6017dfa5..8e3a3d339cd7 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios/FLTGoogleSignInPlugin_Test.h +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/include/google_sign_in_ios/FLTGoogleSignInPlugin_Test.h @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// This header is available in the Test module. Import via "@import google_sign_in.Test;" - #import +@import Foundation; + #import #import "FSIGoogleSignInProtocols.h"