From 785262b45ec3f0216be5cdc48a5c81d55199c5f8 Mon Sep 17 00:00:00 2001 From: "Eric C. Johnson" Date: Wed, 15 Oct 2025 18:51:50 -0600 Subject: [PATCH 1/3] @W-19368408: [MSDK] Local dev instant login (iOS) --- .../Classes/Common/SalesforceSDKManager.m | 12 +++ .../Classes/Test/TestSetupUtils.h | 9 ++ .../Classes/Test/TestSetupUtils.m | 90 +++++++++++-------- 3 files changed, 72 insertions(+), 39 deletions(-) diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m index ac7ae05152..96c4c588d6 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m @@ -231,6 +231,18 @@ + (instancetype)sharedManager { + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ + +#ifdef DEBUG + // For debug app builds only, use test instant log in if applicable. + NSArray *arguments = [[NSProcessInfo processInfo] arguments]; + if ([arguments containsObject:@"-creds"]) { + NSString *creds = arguments[[arguments indexOfObject:@"-creds"] + 1]; + + [TestSetupUtils populateAuthCredentialsFromString:creds]; + [TestSetupUtils synchronousAuthRefresh]; + } +#endif + // For dev support Method sfsdkMotionEndedMethod = class_getInstanceMethod([UIWindow class], @selector(sfsdk_motionEnded:withEvent:)); IMP sfsdkMotionEndedImplementation = method_getImplementation(sfsdkMotionEndedMethod); diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.h b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.h index 7bfd1428ed..4ceca0634e 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.h +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.h @@ -52,6 +52,15 @@ NS_ASSUME_NONNULL_BEGIN */ + (SFSDKTestCredentialsData *)populateAuthCredentialsFromConfigFileForClass:(Class)testClass; +/** + Loads a set of auth credentials from the provided JSON string, and configures + SFUserAccountManager and the current account with the data from that JSON. + @param testCredentialsJsonString The test credentials JSON as a string. + @return The configuration data used to configure SFUserAccountManager (useful e.g. for hybrid + apps which need the data to bootstrap SFHybridViewController). + */ ++ (SFSDKTestCredentialsData *)populateAuthCredentialsFromString:(NSString *)testCredentialsJsonString; + /** Performs a synchronous refresh of the OAuth credentials, which will stage the remaining auth data (access token, User ID, Org ID, etc.) in SFUserAccountManager. diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.m b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.m index 2100789ced..db0b5fd553 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.m +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.m @@ -55,6 +55,55 @@ + (SFSDKTestCredentialsData *)populateAuthCredentialsFromConfigFileForClass:(Cla NSFileManager *fm = [NSFileManager defaultManager]; NSData *tokenJson = [fm contentsAtPath:tokenPath]; id jsonResponse = [SFJsonUtils objectFromJSONData:tokenJson]; + + return [TestSetupUtils authCredentialsFromJson:jsonResponse]; + +} + ++ (SFSDKTestCredentialsData *)populateAuthCredentialsFromString:(NSString *)testCredentialsJsonString { + + return [TestSetupUtils authCredentialsFromJson:[SFJsonUtils objectFromJSONString:testCredentialsJsonString]]; +} + ++ (void)synchronousAuthRefresh +{ + // All of the setup and validation of prerequisite auth state is done in populateAuthCredentialsFromConfigFile. + // Make sure that method has run before this one. + NSAssert(credentials!=nil, @"You must call populateAuthCredentialsFromConfigFileForClass before synchronousAuthRefresh"); + __block SFSDKTestRequestListener *authListener = [[SFSDKTestRequestListener alloc] init]; + __block SFUserAccount *user = nil; + [[SFUserAccountManager sharedInstance] + refreshCredentials:credentials + completion:^(SFOAuthInfo *authInfo, SFUserAccount *userAccount) { + authListener.returnStatus = kTestRequestStatusDidLoad; + user = userAccount; + // Ensure tests don't change/corrupt the current user credentials. + if(user.credentials.refreshToken == nil) { + user.credentials = credentials; + } + } failure:^(SFOAuthInfo *authInfo, NSError *error) { + authListener.lastError = error; + authListener.returnStatus = kTestRequestStatusDidFail; + }]; + [authListener waitForCompletion]; + [[SFUserAccountManager sharedInstance] setCurrentUserInternal:user]; + NSAssert([authListener.returnStatus isEqualToString:kTestRequestStatusDidLoad], @"After auth attempt, expected status '%@', got '%@'", + kTestRequestStatusDidLoad, + authListener.returnStatus); +} + ++ (SFOAuthCredentials *)newClientCredentials { + + NSString *identifier = [[SFUserAccountManager sharedInstance] uniqueUserAccountIdentifier:[SFUserAccountManager sharedInstance].oauthClientId]; + SFOAuthCredentials *creds = [[SFOAuthCredentials alloc] initWithIdentifier:identifier clientId:[SFUserAccountManager sharedInstance].oauthClientId encrypted:YES]; + creds.clientId = [SFUserAccountManager sharedInstance].oauthClientId; + creds.redirectUri = [SFUserAccountManager sharedInstance].oauthCompletionUrl; + creds.domain = [SFUserAccountManager sharedInstance].loginHost; + creds.accessToken = nil; + return creds; +} + ++ (SFSDKTestCredentialsData *)authCredentialsFromJson:(id)jsonResponse { NSAssert(jsonResponse != nil, @"Error parsing JSON from config file: %@", [SFJsonUtils lastError]); NSDictionary *dictResponse = (NSDictionary *)jsonResponse; SFSDKTestCredentialsData *credsData = [[SFSDKTestCredentialsData alloc] initWithDict:dictResponse]; @@ -65,12 +114,12 @@ + (SFSDKTestCredentialsData *)populateAuthCredentialsFromConfigFileForClass:(Cla nil != credsData.identityUrl && nil != credsData.instanceUrl, @"config credentials are missing! %@", dictResponse); - + // check whether the test config file has never been edited NSAssert(![credsData.refreshToken isEqualToString:@"__INSERT_TOKEN_HERE__"], @"You need to obtain credentials for your test org and replace test_credentials.json"); [SalesforceSDKManager initializeSDK]; - + // Note: We need to fix this inconsistency for tests in the long run.There should be a clean way to refresh appConfigs for tests. The configs should apply across all components that need the config. SFSDKAppConfig *appconfig = [[SFSDKAppConfig alloc] init]; appconfig.oauthRedirectURI = credsData.redirectUri; @@ -98,41 +147,4 @@ + (SFSDKTestCredentialsData *)populateAuthCredentialsFromConfigFileForClass:(Cla return credsData; } -+ (void)synchronousAuthRefresh -{ - // All of the setup and validation of prerequisite auth state is done in populateAuthCredentialsFromConfigFile. - // Make sure that method has run before this one. - NSAssert(credentials!=nil, @"You must call populateAuthCredentialsFromConfigFileForClass before synchronousAuthRefresh"); - __block SFSDKTestRequestListener *authListener = [[SFSDKTestRequestListener alloc] init]; - __block SFUserAccount *user = nil; - [[SFUserAccountManager sharedInstance] - refreshCredentials:credentials - completion:^(SFOAuthInfo *authInfo, SFUserAccount *userAccount) { - authListener.returnStatus = kTestRequestStatusDidLoad; - user = userAccount; - // Ensure tests don't change/corrupt the current user credentials. - if(user.credentials.refreshToken == nil) { - user.credentials = credentials; - } - } failure:^(SFOAuthInfo *authInfo, NSError *error) { - authListener.lastError = error; - authListener.returnStatus = kTestRequestStatusDidFail; - }]; - [authListener waitForCompletion]; - [[SFUserAccountManager sharedInstance] setCurrentUserInternal:user]; - NSAssert([authListener.returnStatus isEqualToString:kTestRequestStatusDidLoad], @"After auth attempt, expected status '%@', got '%@'", - kTestRequestStatusDidLoad, - authListener.returnStatus); -} - -+ (SFOAuthCredentials *)newClientCredentials { - - NSString *identifier = [[SFUserAccountManager sharedInstance] uniqueUserAccountIdentifier:[SFUserAccountManager sharedInstance].oauthClientId]; - SFOAuthCredentials *creds = [[SFOAuthCredentials alloc] initWithIdentifier:identifier clientId:[SFUserAccountManager sharedInstance].oauthClientId encrypted:YES]; - creds.clientId = [SFUserAccountManager sharedInstance].oauthClientId; - creds.redirectUri = [SFUserAccountManager sharedInstance].oauthCompletionUrl; - creds.domain = [SFUserAccountManager sharedInstance].loginHost; - creds.accessToken = nil; - return creds; -} @end From f322cf43f99367a7b6198a908463a3a45ad742d6 Mon Sep 17 00:00:00 2001 From: "Eric C. Johnson" Date: Tue, 21 Oct 2025 14:04:57 -0600 Subject: [PATCH 2/3] @W-19368408: [MSDK] Local dev instant login (iOS) (Move Dev Instant Login To SalesforceSDKManager `init`) --- .../Classes/Common/SalesforceSDKManager.m | 21 ++++---- .../Classes/Test/TestSetupUtils.h | 35 +++++++++++--- .../Classes/Test/TestSetupUtils.m | 48 +++++++++++++------ 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m index 96c4c588d6..b855bd9e50 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m @@ -195,6 +195,16 @@ + (void)initialize { + (void)initializeSDK { [self initializeSDKWithClass:InstanceClass]; +#ifdef DEBUG + // For debug app builds only, use test instant log in if applicable. + NSArray *arguments = [[NSProcessInfo processInfo] arguments]; + if ([arguments containsObject:@"-creds"]) { + NSString *creds = arguments[[arguments indexOfObject:@"-creds"] + 1]; + + [TestSetupUtils populateAuthCredentialsFromString:creds initializeSdk:NO]; + [TestSetupUtils synchronousAuthRefreshWithUserDidLoginNotification:YES]; + } +#endif } + (void)initializeSDKWithClass:(Class)className { @@ -231,17 +241,6 @@ + (instancetype)sharedManager { + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - -#ifdef DEBUG - // For debug app builds only, use test instant log in if applicable. - NSArray *arguments = [[NSProcessInfo processInfo] arguments]; - if ([arguments containsObject:@"-creds"]) { - NSString *creds = arguments[[arguments indexOfObject:@"-creds"] + 1]; - - [TestSetupUtils populateAuthCredentialsFromString:creds]; - [TestSetupUtils synchronousAuthRefresh]; - } -#endif // For dev support Method sfsdkMotionEndedMethod = class_getInstanceMethod([UIWindow class], @selector(sfsdk_motionEnded:withEvent:)); diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.h b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.h index 4ceca0634e..db654efbcc 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.h +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.h @@ -55,20 +55,43 @@ NS_ASSUME_NONNULL_BEGIN /** Loads a set of auth credentials from the provided JSON string, and configures SFUserAccountManager and the current account with the data from that JSON. + Salesforce Mobile SDK will be initialized while populating test credentials. @param testCredentialsJsonString The test credentials JSON as a string. - @return The configuration data used to configure SFUserAccountManager (useful e.g. for hybrid - apps which need the data to bootstrap SFHybridViewController). + @return The configuration data used to configure SFUserAccountManager (useful + e.g. for hybrid apps which need the data to bootstrap SFHybridViewController). */ + (SFSDKTestCredentialsData *)populateAuthCredentialsFromString:(NSString *)testCredentialsJsonString; /** - Performs a synchronous refresh of the OAuth credentials, which will stage the remaining auth - data (access token, User ID, Org ID, etc.) in SFUserAccountManager. - `populateAuthCredentialsFromConfigFile` is required to run once before this method will attempt - to refresh authentication. + Loads a set of auth credentials from the provided JSON string, and configures + SFUserAccountManager and the current account with the data from that JSON. + @param testCredentialsJsonString The test credentials JSON as a string. + @param initializeSdk Indicates if Salesfore Mobile SDK should be initialized + while populating test credentials. + @return The configuration data used to configure SFUserAccountManager (useful + e.g. for hybrid apps which need the data to bootstrap SFHybridViewController). + */ ++ (SFSDKTestCredentialsData *)populateAuthCredentialsFromString:(NSString *)testCredentialsJsonString initializeSdk:(BOOL)initializeSdk; + +/** + Performs a synchronous refresh of the OAuth credentials, which will stage the + remaining auth data (access token, User ID, Org ID, etc.) in + SFUserAccountManager. `populateAuthCredentialsFromConfigFile` + is required to run once before this method will attempt to refresh authentication. + The "user did log in" notification will not be posted. */ + (void)synchronousAuthRefresh; +/** + Performs a synchronous refresh of the OAuth credentials, which will stage the + remaining auth data (access token, User ID, Org ID, etc.) in + SFUserAccountManager. `populateAuthCredentialsFromConfigFile` + is required to run once before this method will attempt to refresh authentication. + @param postUserDidLogIn Indicates if the "user did log in" notification should + be posted. + */ ++ (void)synchronousAuthRefreshWithUserDidLoginNotification:(BOOL)postUserDidLogIn; + + (SFOAuthCredentials *)newClientCredentials; @end diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.m b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.m index db0b5fd553..d75a45314d 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.m +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Test/TestSetupUtils.m @@ -56,16 +56,24 @@ + (SFSDKTestCredentialsData *)populateAuthCredentialsFromConfigFileForClass:(Cla NSData *tokenJson = [fm contentsAtPath:tokenPath]; id jsonResponse = [SFJsonUtils objectFromJSONData:tokenJson]; - return [TestSetupUtils authCredentialsFromJson:jsonResponse]; + return [TestSetupUtils authCredentialsFromJson:jsonResponse initializeSdk:YES]; } + (SFSDKTestCredentialsData *)populateAuthCredentialsFromString:(NSString *)testCredentialsJsonString { + return [self populateAuthCredentialsFromString:testCredentialsJsonString initializeSdk:YES]; +} + ++ (SFSDKTestCredentialsData *)populateAuthCredentialsFromString:(NSString *)testCredentialsJsonString initializeSdk:(BOOL)initializeSdk { - return [TestSetupUtils authCredentialsFromJson:[SFJsonUtils objectFromJSONString:testCredentialsJsonString]]; + return [TestSetupUtils authCredentialsFromJson:[SFJsonUtils objectFromJSONString:testCredentialsJsonString] initializeSdk:initializeSdk]; } -+ (void)synchronousAuthRefresh ++ (void)synchronousAuthRefresh { + [self synchronousAuthRefreshWithUserDidLoginNotification:NO]; +} + ++ (void)synchronousAuthRefreshWithUserDidLoginNotification:(BOOL)postUserDidLogIn { // All of the setup and validation of prerequisite auth state is done in populateAuthCredentialsFromConfigFile. // Make sure that method has run before this one. @@ -75,16 +83,24 @@ + (void)synchronousAuthRefresh [[SFUserAccountManager sharedInstance] refreshCredentials:credentials completion:^(SFOAuthInfo *authInfo, SFUserAccount *userAccount) { - authListener.returnStatus = kTestRequestStatusDidLoad; - user = userAccount; - // Ensure tests don't change/corrupt the current user credentials. - if(user.credentials.refreshToken == nil) { - user.credentials = credentials; - } - } failure:^(SFOAuthInfo *authInfo, NSError *error) { - authListener.lastError = error; - authListener.returnStatus = kTestRequestStatusDidFail; - }]; + authListener.returnStatus = kTestRequestStatusDidLoad; + user = userAccount; + // Ensure tests don't change/corrupt the current user credentials. + if (user.credentials.refreshToken == nil) { + user.credentials = credentials; + } + if (postUserDidLogIn) { + NSDictionary *userInfo = @{kSFNotificationUserInfoAccountKey: userAccount, + kSFNotificationUserInfoAuthTypeKey: authInfo}; + [[NSNotificationCenter defaultCenter] postNotificationName:kSFNotificationUserDidLogIn + object:[SFUserAccountManager sharedInstance] + userInfo:userInfo]; + [[SFUserAccountManager sharedInstance] stopCurrentAuthentication:^(BOOL result){}]; + } + } failure:^(SFOAuthInfo *authInfo, NSError *error) { + authListener.lastError = error; + authListener.returnStatus = kTestRequestStatusDidFail; + }]; [authListener waitForCompletion]; [[SFUserAccountManager sharedInstance] setCurrentUserInternal:user]; NSAssert([authListener.returnStatus isEqualToString:kTestRequestStatusDidLoad], @"After auth attempt, expected status '%@', got '%@'", @@ -103,7 +119,7 @@ + (SFOAuthCredentials *)newClientCredentials { return creds; } -+ (SFSDKTestCredentialsData *)authCredentialsFromJson:(id)jsonResponse { ++ (SFSDKTestCredentialsData *)authCredentialsFromJson:(id)jsonResponse initializeSdk:(BOOL)initializeSdk { NSAssert(jsonResponse != nil, @"Error parsing JSON from config file: %@", [SFJsonUtils lastError]); NSDictionary *dictResponse = (NSDictionary *)jsonResponse; SFSDKTestCredentialsData *credsData = [[SFSDKTestCredentialsData alloc] initWithDict:dictResponse]; @@ -118,7 +134,9 @@ + (SFSDKTestCredentialsData *)authCredentialsFromJson:(id)jsonResponse { // check whether the test config file has never been edited NSAssert(![credsData.refreshToken isEqualToString:@"__INSERT_TOKEN_HERE__"], @"You need to obtain credentials for your test org and replace test_credentials.json"); - [SalesforceSDKManager initializeSDK]; + if (initializeSdk) { + [SalesforceSDKManager initializeSDK]; + } // Note: We need to fix this inconsistency for tests in the long run.There should be a clean way to refresh appConfigs for tests. The configs should apply across all components that need the config. SFSDKAppConfig *appconfig = [[SFSDKAppConfig alloc] init]; From b780b888e24c5f5966639de5fd6d38306d1375e2 Mon Sep 17 00:00:00 2001 From: "Eric C. Johnson" Date: Tue, 21 Oct 2025 16:00:17 -0600 Subject: [PATCH 3/3] @W-19368408: [MSDK] Local dev instant login (iOS) (Exclude `TestSetupUtils` From Code Coverage) --- .codecov.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 0410dd13d0..f6e03a6c06 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -80,6 +80,7 @@ ignore: - "shared" - "native" - "libs/**/*Test*/*" + - "**/TestSetupUtils*" flag_management: default_rules: # the rules that will be followed for any flag added, generally @@ -110,4 +111,4 @@ component_management: - "libs/SmartStore/SmartStore/" - component_id: MobileSync paths: - - "libs/MobileSync/MobileSync/" \ No newline at end of file + - "libs/MobileSync/MobileSync/"