Skip to content

Commit d912860

Browse files
committed
add E2E test classe for AuthProvider
Signed-off-by: Rupesh J <rupesh.j@salesforce.com>
1 parent 817c67f commit d912860

3 files changed

Lines changed: 162 additions & 66 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import assert from 'assert';
2+
import * as child from 'child_process';
3+
import { fileURLToPath } from 'node:url';
4+
import * as path from 'path';
5+
import { type Config, AuthProviders } from './index.js';
6+
7+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
8+
9+
describe(AuthProviders.name, function () {
10+
this.timeout('10m');
11+
let plugin: AuthProviders;
12+
13+
before(() => {
14+
plugin = new AuthProviders(global.browserforce);
15+
});
16+
17+
const configWithSecretAndKey: Config = {
18+
TestAuthProvider: {
19+
consumerSecret: 'test-secret-12345',
20+
consumerKey: 'test-key-67890',
21+
},
22+
};
23+
24+
const configWithSecretOnly: Config = {
25+
TestAuthProvider: {
26+
consumerSecret: 'updated-secret-abcde',
27+
},
28+
};
29+
30+
const configWithKeyOnly: Config = {
31+
TestAuthProvider: {
32+
consumerKey: 'updated-key-fghij',
33+
},
34+
};
35+
36+
const configEmpty: Config = {
37+
TestAuthProvider: {},
38+
};
39+
40+
it('should deploy an AuthProvider for testing', () => {
41+
const sourceDeployCmd = child.spawnSync('sf', [
42+
'project',
43+
'deploy',
44+
'start',
45+
'-d',
46+
path.join(__dirname, 'sfdx-source'),
47+
'--json',
48+
]);
49+
assert.deepStrictEqual(sourceDeployCmd.status, 0, sourceDeployCmd.output.toString());
50+
});
51+
52+
it('should update consumerSecret and consumerKey', async () => {
53+
await plugin.apply(configWithSecretAndKey);
54+
// Note: retrieve() returns empty config, so we can only verify apply completes without errors
55+
});
56+
57+
it('should update consumerSecret only', async () => {
58+
await plugin.apply(configWithSecretOnly);
59+
});
60+
61+
it('should update consumerKey only', async () => {
62+
await plugin.apply(configWithKeyOnly);
63+
});
64+
65+
it('should handle empty config without errors', async () => {
66+
await plugin.apply(configEmpty);
67+
});
68+
69+
it('should throw an error when AuthProvider does not exist', async () => {
70+
const configInvalid: Config = {
71+
NonExistentAuthProvider: {
72+
consumerSecret: 'test-secret',
73+
consumerKey: 'test-key',
74+
},
75+
};
76+
let err;
77+
try {
78+
await plugin.apply(configInvalid);
79+
} catch (e) {
80+
err = e;
81+
}
82+
assert.throws(() => {
83+
throw err;
84+
}, /No AuthProviders found with DeveloperNames/);
85+
});
86+
87+
it('should remove the testing AuthProvider', async () => {
88+
await global.browserforce.connection.metadata.delete('AuthProvider', ['TestAuthProvider']);
89+
});
90+
});
Lines changed: 54 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1+
import { type SalesforceUrlPath, waitForPageErrors } from '../../browserforce.js';
12
import { BrowserforcePlugin } from '../../plugin.js';
23

34
const CONSUMER_SECRET_SELECTOR = '#ConsumerSecret';
45
const CONSUMER_KEY_SELECTOR = '#ConsumerKey';
56
const SAVE_BUTTON_SELECTOR = 'input[id$=":saveBtn"], #topButtonRow > input[name="save"], button[title="Save"]';
67

7-
const getUrl = (orgId: string) => `/${orgId}/e`;
8+
const getUrl = (orgId: string): SalesforceUrlPath => `/${orgId}/e` as SalesforceUrlPath;
89

910
type AuthProviderConfig = {
1011
consumerSecret?: string;
1112
consumerKey?: string;
1213
};
1314

14-
type Config = {
15+
export type Config = {
1516
[developerName: string]: AuthProviderConfig;
1617
};
1718

@@ -35,11 +36,9 @@ export class AuthProviders extends BrowserforcePlugin {
3536
const developerNamesList = developerNames.map((name) => `'${name}'`).join(',');
3637

3738
// Query AuthProviders using standard REST API (not Tooling API)
38-
const authProviders = await this.org
39-
.getConnection()
40-
.query<AuthProviderRecord>(
41-
`SELECT Id, DeveloperName FROM AuthProvider WHERE DeveloperName IN (${developerNamesList})`,
42-
);
39+
const authProviders = await this.browserforce.connection.query<AuthProviderRecord>(
40+
`SELECT Id, DeveloperName FROM AuthProvider WHERE DeveloperName IN (${developerNamesList})`,
41+
);
4342

4443
if (authProviders.records.length === 0) {
4544
throw new Error(`No AuthProviders found with DeveloperNames: ${developerNames.join(', ')}`);
@@ -73,79 +72,68 @@ export class AuthProviders extends BrowserforcePlugin {
7372
this.browserforce.logger?.log('editPageUrl', editPageUrl);
7473
console.log(`[AuthProviders] Navigating to edit page for ${developerName}: ${editPageUrl}`);
7574

76-
const page = await this.browserforce.openPage(editPageUrl);
75+
await using page = await this.browserforce.openPage(editPageUrl);
7776

7877
// Wait for the page/frame to load - handle both Lightning (iframe) and Classic UI
7978
// Use ConsumerSecret as the selector to wait for, or fallback to ConsumerKey
80-
const formSelector = `${CONSUMER_SECRET_SELECTOR}, ${CONSUMER_KEY_SELECTOR}, form`;
79+
const formSelector = `${CONSUMER_SECRET_SELECTOR}, ${CONSUMER_KEY_SELECTOR}`;
8180
const frameOrPage = await this.browserforce.waitForSelectorInFrameOrPage(page, formSelector);
8281

8382
try {
84-
// Update ConsumerSecret if provided
85-
if (authProviderConfig.consumerSecret !== undefined) {
86-
await frameOrPage.waitForSelector(CONSUMER_SECRET_SELECTOR, { timeout: 10000 });
87-
await frameOrPage.$eval(
88-
CONSUMER_SECRET_SELECTOR,
89-
(e: HTMLInputElement, v: string) => {
90-
e.value = v;
91-
},
92-
authProviderConfig.consumerSecret,
93-
);
94-
}
83+
// Check if there's anything to update
84+
const hasUpdates = authProviderConfig.consumerSecret !== undefined || authProviderConfig.consumerKey !== undefined;
85+
86+
if (hasUpdates) {
87+
// Update ConsumerSecret if provided
88+
if (authProviderConfig.consumerSecret !== undefined) {
89+
await frameOrPage.locator(CONSUMER_SECRET_SELECTOR).waitFor({ timeout: 10000 });
90+
await frameOrPage.locator(CONSUMER_SECRET_SELECTOR).fill(authProviderConfig.consumerSecret);
91+
}
9592

96-
// Update ConsumerKey if provided
97-
if (authProviderConfig.consumerKey !== undefined) {
98-
await frameOrPage.waitForSelector(CONSUMER_KEY_SELECTOR, { timeout: 10000 });
99-
await frameOrPage.$eval(
100-
CONSUMER_KEY_SELECTOR,
101-
(e: HTMLInputElement, v: string) => {
102-
e.value = v;
103-
},
104-
authProviderConfig.consumerKey,
105-
);
106-
}
93+
// Update ConsumerKey if provided
94+
if (authProviderConfig.consumerKey !== undefined) {
95+
await frameOrPage.locator(CONSUMER_KEY_SELECTOR).waitFor({ timeout: 10000 });
96+
await frameOrPage.locator(CONSUMER_KEY_SELECTOR).fill(authProviderConfig.consumerKey);
97+
}
10798

108-
// Save the changes
109-
await frameOrPage.waitForSelector(SAVE_BUTTON_SELECTOR, { timeout: 10000 });
110-
111-
// Click save button - don't wait for navigation as it may redirect to a non-existent page
112-
// Instead, wait for the click to complete and then check for errors
113-
const saveButton = await frameOrPage.$(SAVE_BUTTON_SELECTOR);
114-
if (!saveButton) {
115-
throw new Error(`Save button not found for AuthProvider '${developerName}'`);
116-
}
117-
118-
// Click the save button
119-
await saveButton.click();
120-
121-
// Wait for save to complete - give it time to process
122-
// The page might reload or show a success/error message
123-
await new Promise((resolve) => setTimeout(resolve, 3000));
124-
125-
// Check for errors on the page/frame
126-
// If the frame still exists, check it; otherwise check the main page
127-
try {
128-
// Try to check for errors in the frame first
129-
const errorElements = await frameOrPage.$$('div.errorMsg, div.error, .errorMessage, #errorTitle');
130-
if (errorElements.length > 0) {
131-
const errorText = await frameOrPage.evaluate(() => {
132-
const errorDiv = document.querySelector('div.errorMsg, div.error, .errorMessage, #errorTitle');
133-
return errorDiv ? errorDiv.textContent : null;
134-
});
135-
if (errorText && !errorText.includes('page no longer exists')) {
136-
throw new Error(`Save failed: ${errorText}`);
99+
// Save the changes
100+
await frameOrPage.locator(SAVE_BUTTON_SELECTOR).waitFor({ timeout: 10000 });
101+
102+
// Click save button - don't wait for navigation as it may redirect to a non-existent page
103+
// Instead, wait for the click to complete and then check for errors
104+
const saveButtonLocator = frameOrPage.locator(SAVE_BUTTON_SELECTOR);
105+
const saveButtonCount = await saveButtonLocator.count();
106+
if (saveButtonCount === 0) {
107+
throw new Error(`Save button not found for AuthProvider '${developerName}'`);
108+
}
109+
110+
// Click the save button
111+
await saveButtonLocator.first().click();
112+
113+
// Wait for save to complete - give it time to process
114+
// The page might reload or show a success/error message
115+
await new Promise((resolve) => setTimeout(resolve, 3000));
116+
117+
// Check for errors on the page/frame
118+
// If the frame still exists, check it; otherwise check the main page
119+
try {
120+
// Try to check for errors in the frame first
121+
const errorLocator = frameOrPage.locator('div.errorMsg, div.error, .errorMessage, #errorTitle');
122+
const errorCount = await errorLocator.count();
123+
if (errorCount > 0) {
124+
const errorText = await errorLocator.first().textContent();
125+
if (errorText && !errorText.includes('page no longer exists')) {
126+
throw new Error(`Save failed: ${errorText.trim()}`);
127+
}
137128
}
129+
} catch (e) {
130+
// If checking frame fails, check the main page
131+
await waitForPageErrors(page);
138132
}
139-
} catch (e) {
140-
// If checking frame fails, check the main page
141-
await this.browserforce.throwPageErrors(page);
142133
}
143134
} catch (error) {
144-
await page.close();
145135
throw new Error(`Failed to update AuthProvider '${developerName}': ${error.message}`);
146136
}
147-
148-
await page.close();
149137
}
150138
}
151139
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<AuthProvider xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<friendlyName>TestAuthProvider</friendlyName>
4+
<includeOrgIdInIdentifier>true</includeOrgIdInIdentifier>
5+
<isPkceEnabled>true</isPkceEnabled>
6+
<providerType>OpenIdConnect</providerType>
7+
<requireMfa>false</requireMfa>
8+
<sendClientCredentialsInHeader>false</sendClientCredentialsInHeader>
9+
<sendSecretInApis>true</sendSecretInApis>
10+
<authorizeUrl>https://login.openid.com/services/oauth2/authorize</authorizeUrl>
11+
<consumerKey>3MVG9k9cKU2O1H29X8gT08Ks2jVjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92WjH6Kd92W</consumerKey>
12+
<consumerSecret>Placeholder_Value</consumerSecret>
13+
<defaultScopes>openid profile</defaultScopes>
14+
<idTokenIssuer>https://login.openid.com</idTokenIssuer>
15+
<sendAccessTokenInHeader>true</sendAccessTokenInHeader>
16+
<tokenUrl>https://login.openid.com/services/oauth2/token</tokenUrl>
17+
<userInfoUrl>https://login.openid.com/services/oauth2/userinfo</userInfoUrl>
18+
</AuthProvider>

0 commit comments

Comments
 (0)