Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
dc69440
Beginning of UI tests for AuthFlowTester
wmathurin Nov 22, 2025
16e6815
Merge branch 'dev' of github.com:wmathurin/SalesforceMobileSDK-iOS in…
wmathurin Nov 25, 2025
90ebe3c
Basic login / logout tests
wmathurin Nov 26, 2025
c0a4e06
Merge branch 'dev' of github.com:wmathurin/SalesforceMobileSDK-iOS in…
wmathurin Nov 26, 2025
16df64d
Basic test brings up login options and set the static configuration i…
wmathurin Nov 26, 2025
567e87e
Merge branch 'dev' of github.com:forcedotcom/SalesforceMobileSDK-iOS …
wmathurin Dec 2, 2025
df83b95
Login tests:
wmathurin Dec 2, 2025
516afad
Speeding up tests and checking SIDs (for hybrid flows)
wmathurin Dec 2, 2025
be4d829
Added tests involving refresh token migration
wmathurin Dec 3, 2025
5849109
More tests - refactored test support code also
wmathurin Dec 4, 2025
355c074
Updated test_config.json.sample
wmathurin Dec 4, 2025
8d3ab2a
Recreating the oauthRequest for the authSession when changing login o…
wmathurin Dec 4, 2025
3a3c10b
Adding import button in BootConfigEditor and export buttons in UserCr…
wmathurin Dec 5, 2025
1689251
More detailled assertion failures
wmathurin Dec 5, 2025
d8968e9
Additional comments
wmathurin Dec 5, 2025
b72a051
Fixing errors in testing code
wmathurin Dec 5, 2025
3e32c7b
Added tests that use dynamic configuration also
wmathurin Dec 6, 2025
97a5241
Multi user testing (WIP)
wmathurin Dec 6, 2025
d504ac5
Re-organizing login tests - removing some tests
wmathurin Dec 6, 2025
2018ae3
Re-organized tests / prune some / added a few (multi user)
wmathurin Dec 6, 2025
8cc0513
Overview.md for the UI tests
wmathurin Dec 6, 2025
7580c21
Changes to overview
wmathurin Dec 6, 2025
5602b3b
Turning on coverage in scheme
wmathurin Dec 10, 2025
c6f192a
Misc refactoring / fixes
wmathurin Dec 10, 2025
478f325
Code coverage in SalesforceSDKCore also
wmathurin Dec 10, 2025
0911cff
Fixing switch user code in UI tests
wmathurin Dec 11, 2025
a1f0a01
More readable helper test code
wmathurin Dec 11, 2025
6ebd6de
Cleaner helper method in base class - new documentation
wmathurin Dec 11, 2025
fa026d7
More fixes
wmathurin Dec 11, 2025
cd376fe
More fixes
wmathurin Dec 12, 2025
b8621b3
More changes
wmathurin Dec 13, 2025
e98b7f6
Not hardcoding .subset definition
wmathurin Dec 13, 2025
20bb5fa
Beacon login tests now all passing
wmathurin Dec 16, 2025
9fe5c7a
Somehow server does not return api_instance_url for user agent flow +…
wmathurin Dec 16, 2025
b337f6d
Merge branch 'dev' of github.com:wmathurin/SalesforceMobileSDK-iOS in…
wmathurin Dec 16, 2025
efa466a
Cleaning up signatures of helper methods - nil dynamic app config <=>…
wmathurin Dec 16, 2025
3d160fc
Updating overview.md for UI tests
wmathurin Dec 16, 2025
086e197
Only doing validation after login in the dedicated login tests (not i…
wmathurin Dec 16, 2025
e6589a9
Fixing an actual SDK bug: scopes from dynamic config were NOT being u…
wmathurin Dec 16, 2025
8b8749f
Turning on parallelization when running tests
wmathurin Dec 16, 2025
8848eba
Merge branch 'dev' of github.com:wmathurin/SalesforceMobileSDK-iOS in…
wmathurin Dec 16, 2025
7dc8c11
Scopes were not passed through during migration
wmathurin Dec 17, 2025
323b5e6
During restart in some login tests
wmathurin Dec 17, 2025
5d081dc
Only failures are migration from beacon app.
wmathurin Dec 18, 2025
33074eb
Merge branch 'dev' of github.com:wmathurin/SalesforceMobileSDK-iOS in…
wmathurin Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ - (NSString *)devInfoTitleString
[presentedViewController dismissViewControllerAnimated:YES completion:^{
// Restart authentication with the updated configuration
if ([presentedViewController isKindOfClass:[SFLoginViewController class]]) {
[[SFUserAccountManager sharedInstance] restartAuthenticationForViewController:(SFLoginViewController *)presentedViewController];
[[SFUserAccountManager sharedInstance] restartAuthenticationForViewController:(SFLoginViewController *)presentedViewController recreateAuthRequest:YES];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Login options might change the consumer key / callback url / scopes so we need to restart the oauth request otherwise the new settings will not be used.

}
// TODO support advanced auth case
}];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@

import SwiftUI

// MARK: - JSON Import Labels
public struct BootConfigJSONKeys {
public static let consumerKey = "remoteAccessConsumerKey"
public static let redirectUri = "oauthRedirectURI"
public static let scopes = "scopes"
}

public struct BootConfigEditor: View {
let title: String
let buttonLabel: String
Expand All @@ -38,6 +45,8 @@ public struct BootConfigEditor: View {
let onUseConfig: () -> Void
let initiallyExpanded: Bool
@State private var isExpanded: Bool = false
@State private var showImportAlert: Bool = false
@State private var importJSONText: String = ""

public init(
title: String,
Expand All @@ -63,21 +72,32 @@ public struct BootConfigEditor: View {

public var body: some View {
VStack(alignment: .leading, spacing: 12) {
Button(action: {
withAnimation {
isExpanded.toggle()
HStack {
Button(action: {
withAnimation {
isExpanded.toggle()
}
}) {
HStack {
Text(title)
.font(.headline)
.foregroundColor(.primary)
Spacer()
Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
.foregroundColor(.secondary)
}
}
}) {
HStack {
Text(title)
.font(.headline)
.foregroundColor(.primary)
Spacer()
Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
.foregroundColor(.secondary)
Button(action: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding an import button:

  • manual tester could paste a boot config
  • UI tests make use of it - it saves on a few taps and speed up the tests a bit

importJSONText = ""
showImportAlert = true
}) {
Image(systemName: "square.and.arrow.down")
.font(.subheadline)
.foregroundColor(.blue)
}
.padding(.horizontal)
.accessibilityIdentifier("importConfigButton")
}
.padding(.horizontal)

if isExpanded {
VStack(alignment: .leading, spacing: 8) {
Expand Down Expand Up @@ -130,6 +150,34 @@ public struct BootConfigEditor: View {
.onAppear {
isExpanded = initiallyExpanded
}
.alert("Import Configuration", isPresented: $showImportAlert) {
TextField("Paste JSON here", text: $importJSONText)
Button("Import") {
importConfigFromJSON()
}
Button("Cancel", role: .cancel) { }
} message: {
Text("Paste JSON with remoteAccessConsumerKey, oauthRedirectURI, and scopes")
}
}

// MARK: - Helper Methods

private func importConfigFromJSON() {
guard let jsonData = importJSONText.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else {
return
}

if let key = json[BootConfigJSONKeys.consumerKey] as? String {
consumerKey = key
}
if let uri = json[BootConfigJSONKeys.redirectUri] as? String {
callbackUrl = uri
}
if let scopesValue = json[BootConfigJSONKeys.scopes] as? String {
scopes = scopesValue
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ NS_SWIFT_NAME(SalesforceLoginViewControllerDelegate)

- (void)loginViewControllerDidReload:(nonnull SFLoginViewController *)loginViewController;

- (void)loginViewControllerDidChangeLoginOptions:(nonnull SFLoginViewController *)loginViewController;


@end

/** The Salesforce login screen view.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,8 @@ - (UIBarButtonItem *)createSettingsButton {
handler:^(__kindof UIAction* _Nonnull action) {
UIViewController *configPicker = [BootConfigPickerViewController makeViewControllerOnConfigurationCompleted:^{
[self dismissViewControllerAnimated:YES completion:^{
if ([self.delegate respondsToSelector:@selector(loginViewControllerDidReload:)]) {
[self.delegate loginViewControllerDidReload:self];
if ([self.delegate respondsToSelector:@selector(loginViewControllerDidChangeLoginOptions:)]) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reload is not enough - we need the auth request within the auth session to be recreated (see earlier comment).

[self.delegate loginViewControllerDidChangeLoginOptions:self];
}
}];
}];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ NS_ASSUME_NONNULL_BEGIN
* Returns the scope query parameter string for OAuth requests.
* @return A properly formatted scope parameter string, or empty string if no scopes provided.
*/
- (NSString *)scopeQueryParamString;
- (NSString *)scopeQueryParamString:(NSArray<NSString*>*)scopes;

/**
Migrates the refresh token for a user to a new app configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ - (NSString *)approvalURLForEndpoint:(NSString *)authorizeEndpoint
}

// OAuth scopes
NSString *scopeString = [self scopeQueryParamString];
NSString *scopeString = [self scopeQueryParamString:credentials.scopes];
if (scopeString.length > 0) {
[approvalUrlString appendString:scopeString];
}
Expand All @@ -842,9 +842,9 @@ -(void) clearFrontDoorBridgeLoginOverride {
self.frontdoorBridgeLoginOverride = nil;
}

- (NSString *)scopeQueryParamString {
if (self.scopes.count > 0) {
NSString *scopeStr = [SFScopeParser computeScopeParameterWithURLEncodingWithScopes:self.scopes];
- (NSString *)scopeQueryParamString:(NSArray<NSString*>*)scopes {
if (scopes.count > 0) {
NSString *scopeStr = [SFScopeParser computeScopeParameterWithURLEncodingWithScopes:[NSSet setWithArray:scopes]];
return [NSString stringWithFormat:@"&%@=%@", kSFOAuthScope, scopeStr];
} else {
return @"";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ - (SFOAuthCredentials *)newClientCredentials {
creds.clientId = self.oauthRequest.oauthClientId;
creds.redirectUri = self.oauthRequest.oauthCompletionUrl;
creds.domain = self.oauthRequest.loginHost;
creds.scopes = [self.oauthRequest.scopes allObjects];
creds.accessToken = nil;
return creds;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ Set this block to handle presentation of the Authentication View Controller.

- (void)restartAuthenticationForViewController:(SFLoginViewController *)loginViewController;

- (void)restartAuthenticationForViewController:(SFLoginViewController *)loginViewController recreateAuthRequest:(BOOL)recreateAuthRequest;


@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ -(SFSDKAuthRequest *)migrateRefreshAuthRequest:(SFSDKAppConfig *)newAppConfig {
request.additionalOAuthParameterKeys = self.additionalOAuthParameterKeys;
request.oauthClientId = newAppConfig.remoteAccessConsumerKey;
request.oauthCompletionUrl = newAppConfig.oauthRedirectURI;
request.scopes = newAppConfig.oauthScopes;
request.scene = [[SFSDKWindowManager sharedManager] defaultScene];
return request;
}
Expand Down Expand Up @@ -1119,9 +1120,26 @@ - (void)loginViewControllerDidReload:(SFLoginViewController *)loginViewControlle
[self restartAuthenticationForViewController:loginViewController];
}

- (void)loginViewControllerDidChangeLoginOptions:(SFLoginViewController *)loginViewController {
[self restartAuthenticationForViewController:loginViewController recreateAuthRequest:YES];
}

- (void)restartAuthenticationForViewController:(SFLoginViewController *)loginViewController {
[self restartAuthenticationForViewController:loginViewController recreateAuthRequest:NO];
}

- (void)restartAuthenticationForViewController:(SFLoginViewController *)loginViewController recreateAuthRequest:(BOOL)recreateAuthRequest {
NSString *sceneId = loginViewController.view.window.windowScene.session.persistentIdentifier;
[self restartAuthentication:self.authSessions[sceneId]];

SFSDKAuthSession* session = self.authSessions[sceneId];

// Recreate the oauth request
// Otherwise changes to consumer key / callback url or scopes will not get picked up
if (recreateAuthRequest) {
session.oauthRequest = [self defaultAuthRequestWithLoginHost:session.oauthRequest.loginHost];
}

[self restartAuthentication:session];
}

#pragma mark - SFSDKLoginHostDelegate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,10 +464,10 @@ - (void)testDefaultTokenEncryption {
- (void)testScopeQueryParamStringEmptyScopes {
// Given
SFOAuthCoordinator *coordinator = [[SFOAuthCoordinator alloc] init];
coordinator.scopes = [NSSet set];
NSArray<NSString*> *scopes = [NSArray array];

// When
NSString *result = [coordinator scopeQueryParamString];
NSString *result = [coordinator scopeQueryParamString:scopes];

// Then
XCTAssertEqualObjects(result, @"", @"Empty scopes should return empty string");
Expand All @@ -476,10 +476,10 @@ - (void)testScopeQueryParamStringEmptyScopes {
- (void)testScopeQueryParamStringNilScopes {
// Given
SFOAuthCoordinator *coordinator = [[SFOAuthCoordinator alloc] init];
coordinator.scopes = nil;
NSArray<NSString*> *scopes = nil;

// When
NSString *result = [coordinator scopeQueryParamString];
NSString *result = [coordinator scopeQueryParamString:scopes];

// Then
XCTAssertEqualObjects(result, @"", @"Nil scopes should return empty string");
Expand All @@ -488,10 +488,10 @@ - (void)testScopeQueryParamStringNilScopes {
- (void)testScopeQueryParamStringSingleScope {
// Given
SFOAuthCoordinator *coordinator = [[SFOAuthCoordinator alloc] init];
coordinator.scopes = [NSSet setWithObject:@"web"];
NSArray<NSString*> *scopes = @[@"web"];

// When
NSString *result = [coordinator scopeQueryParamString];
NSString *result = [coordinator scopeQueryParamString:scopes];

// Then
// Should include refresh_token and the provided scope, URL encoded
Expand All @@ -501,10 +501,10 @@ - (void)testScopeQueryParamStringSingleScope {
- (void)testScopeQueryParamStringMultipleScopes {
// Given
SFOAuthCoordinator *coordinator = [[SFOAuthCoordinator alloc] init];
coordinator.scopes = [NSSet setWithObjects:@"web", @"api", @"id", nil];
NSArray<NSString*> *scopes = @[@"web", @"api", @"id"];

// When
NSString *result = [coordinator scopeQueryParamString];
NSString *result = [coordinator scopeQueryParamString:scopes];

// Then
// Should include refresh_token and all provided scopes, sorted and URL encoded
Expand All @@ -514,10 +514,10 @@ - (void)testScopeQueryParamStringMultipleScopes {
- (void)testScopeQueryParamStringWithRefreshTokenAlreadyPresent {
// Given
SFOAuthCoordinator *coordinator = [[SFOAuthCoordinator alloc] init];
coordinator.scopes = [NSSet setWithObjects:@"web", @"api", @"refresh_token", nil];
NSArray<NSString*> *scopes = @[@"web", @"api", @"refresh_token"];

// When
NSString *result = [coordinator scopeQueryParamString];
NSString *result = [coordinator scopeQueryParamString:scopes];

// Then
// Should still work correctly even if refresh_token is already present
Expand Down
Loading
Loading