mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
chore(test): add test cases for SAML and OIDC SSO configuration (#5093)
This commit is contained in:
parent
2f0e2a4400
commit
4a517379b3
3 changed files with 108 additions and 4 deletions
|
@ -1,6 +1,7 @@
|
||||||
import { conditionalString } from '@silverhand/essentials';
|
import { conditional, conditionalString } from '@silverhand/essentials';
|
||||||
import { type Page } from 'puppeteer';
|
import { type Page } from 'puppeteer';
|
||||||
|
|
||||||
|
import { expectToSaveChanges, waitForToast } from '#src/ui-helpers/index.js';
|
||||||
import { dcls, cls } from '#src/utils.js';
|
import { dcls, cls } from '#src/utils.js';
|
||||||
|
|
||||||
import { type SsoConnectorTestCase, type Protocol } from './sso-connectors-test-cases.js';
|
import { type SsoConnectorTestCase, type Protocol } from './sso-connectors-test-cases.js';
|
||||||
|
@ -68,6 +69,65 @@ const checkSsoConnectorConnectionTabInfo = async (page: Page, protocol: Protocol
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkOidcConfigPreview = async (previewResults: Record<string, string>) => {
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(previewResults).map(async ([key, value]) => {
|
||||||
|
const valueField = await expect(page).toMatchElement(
|
||||||
|
[`${dcls('container')}${cls('oidcConfigPreview')}`, `div:has(${dcls('title')})`].join(' '),
|
||||||
|
{ text: key }
|
||||||
|
);
|
||||||
|
const valueDisplayed = await valueField.$eval(
|
||||||
|
[dcls('content')].join(' '),
|
||||||
|
(element) => element.textContent
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(valueDisplayed).toBe(value);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkSamlConfigPreview = async (previewResults: Record<string, string>) => {
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(previewResults).map(async ([key, value]) => {
|
||||||
|
const valueField = await expect(page).toMatchElement(
|
||||||
|
[`${dcls('samlMetadataForm')}`, `${dcls('container')}`, `div:has(${dcls('title')})`].join(
|
||||||
|
' '
|
||||||
|
),
|
||||||
|
{ text: key }
|
||||||
|
);
|
||||||
|
const valueDisplayed = await valueField.$eval(
|
||||||
|
[
|
||||||
|
dcls('content'),
|
||||||
|
conditional(key === 'Signing certificate' && dcls('certificatePreview')),
|
||||||
|
].join(' '),
|
||||||
|
(element) => element.textContent
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(valueDisplayed).toBe(value);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configure the SSO connector connection and check the validity by comparing the preview results.
|
||||||
|
const configureSsoConnectorConnection = async (
|
||||||
|
page: Page,
|
||||||
|
formData: Record<string, string>,
|
||||||
|
protocol: Protocol,
|
||||||
|
previewResults: Record<string, string>
|
||||||
|
) => {
|
||||||
|
await expect(page).toFillForm(dcls('form'), formData);
|
||||||
|
await expectToSaveChanges(page);
|
||||||
|
await waitForToast(page, { text: 'Saved' });
|
||||||
|
|
||||||
|
if (protocol === 'OIDC') {
|
||||||
|
await checkOidcConfigPreview(previewResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protocol === 'SAML') {
|
||||||
|
await checkSamlConfigPreview(previewResults);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const findModalFooterButton = async (isButtonDisabled = false) => {
|
export const findModalFooterButton = async (isButtonDisabled = false) => {
|
||||||
return page.waitForSelector(
|
return page.waitForSelector(
|
||||||
`.ReactModalPortal div[class$=footer] button${conditionalString(
|
`.ReactModalPortal div[class$=footer] button${conditionalString(
|
||||||
|
@ -78,7 +138,7 @@ export const findModalFooterButton = async (isButtonDisabled = false) => {
|
||||||
|
|
||||||
export const fillSsoConnectorCreationModal = async (
|
export const fillSsoConnectorCreationModal = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
{ connectorFactoryName, connectorName, protocol }: SsoConnectorTestCase,
|
{ connectorFactoryName, connectorName, protocol, formData, previewResults }: SsoConnectorTestCase,
|
||||||
checkConnectionInfo = false
|
checkConnectionInfo = false
|
||||||
) => {
|
) => {
|
||||||
// Button should be disabled util form is filled.
|
// Button should be disabled util form is filled.
|
||||||
|
@ -114,5 +174,7 @@ export const fillSsoConnectorCreationModal = async (
|
||||||
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||||
|
|
||||||
await checkSsoConnectorConnectionTabInfo(page, protocol);
|
await checkSsoConnectorConnectionTabInfo(page, protocol);
|
||||||
|
|
||||||
|
await configureSsoConnectorConnection(page, formData, protocol, previewResults);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,33 @@ export type SsoConnectorTestCase = {
|
||||||
connectorName: string;
|
connectorName: string;
|
||||||
connectorFactoryName: string;
|
connectorFactoryName: string;
|
||||||
protocol: Protocol;
|
protocol: Protocol;
|
||||||
|
formData: Record<string, string>;
|
||||||
|
previewResults: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const oidcFormData = {
|
||||||
|
clientId: 'client-id',
|
||||||
|
clientSecret: 'client-secret',
|
||||||
|
};
|
||||||
|
|
||||||
|
const samlFormData = {
|
||||||
|
// This is a real metadata URL from Microsoft Entra ID test application.
|
||||||
|
metadataUrl:
|
||||||
|
'https://login.microsoftonline.com/ac016212-4f8d-46c6-892c-57c90a255a02/federationmetadata/2007-06/federationmetadata.xml?appid=f562b098-dc8c-4e20-8c4b-ea55554fbd8c',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const oidcPreviewResults = {
|
||||||
|
'Authorization endpoint': 'https://accounts.google.com/o/oauth2/v2/auth',
|
||||||
|
'Token endpoint': 'https://oauth2.googleapis.com/token',
|
||||||
|
'User information endpoint': 'https://openidconnect.googleapis.com/v1/userinfo',
|
||||||
|
'JSON web key set endpoint': 'https://www.googleapis.com/oauth2/v3/certs',
|
||||||
|
Issuer: 'https://accounts.google.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const samlPreviewResults = {
|
||||||
|
'Sign on URL': 'https://login.microsoftonline.com/ac016212-4f8d-46c6-892c-57c90a255a02/saml2',
|
||||||
|
Issuer: 'https://sts.windows.net/ac016212-4f8d-46c6-892c-57c90a255a02/',
|
||||||
|
'Signing certificate': 'Expiring Sunday, October 25, 2026',
|
||||||
};
|
};
|
||||||
|
|
||||||
const microsoftEntraIdName = 'Microsoft Entra ID';
|
const microsoftEntraIdName = 'Microsoft Entra ID';
|
||||||
|
@ -12,6 +39,8 @@ const microsoftEntraID: SsoConnectorTestCase = {
|
||||||
connectorName: microsoftEntraIdName,
|
connectorName: microsoftEntraIdName,
|
||||||
connectorFactoryName: microsoftEntraIdName,
|
connectorFactoryName: microsoftEntraIdName,
|
||||||
protocol: 'SAML',
|
protocol: 'SAML',
|
||||||
|
formData: samlFormData,
|
||||||
|
previewResults: samlPreviewResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
const googleWorkspaceName = 'Google Workspace';
|
const googleWorkspaceName = 'Google Workspace';
|
||||||
|
@ -19,6 +48,8 @@ const googleWorkspace: SsoConnectorTestCase = {
|
||||||
connectorName: googleWorkspaceName,
|
connectorName: googleWorkspaceName,
|
||||||
connectorFactoryName: googleWorkspaceName,
|
connectorFactoryName: googleWorkspaceName,
|
||||||
protocol: 'OIDC',
|
protocol: 'OIDC',
|
||||||
|
formData: oidcFormData,
|
||||||
|
previewResults: oidcPreviewResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
const oktaName = 'Okta';
|
const oktaName = 'Okta';
|
||||||
|
@ -26,6 +57,9 @@ const okta: SsoConnectorTestCase = {
|
||||||
connectorName: oktaName,
|
connectorName: oktaName,
|
||||||
connectorFactoryName: oktaName,
|
connectorFactoryName: oktaName,
|
||||||
protocol: 'OIDC',
|
protocol: 'OIDC',
|
||||||
|
// Google Workspace connector have `issuer` predefined, for other OIDC-protocol-based connectors, we use Google's issuer to test the config fetcher and preview.
|
||||||
|
formData: { ...oidcFormData, issuer: 'https://accounts.google.com' },
|
||||||
|
previewResults: oidcPreviewResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
const oidcName = 'OIDC';
|
const oidcName = 'OIDC';
|
||||||
|
@ -33,6 +67,9 @@ const oidc: SsoConnectorTestCase = {
|
||||||
connectorName: oidcName,
|
connectorName: oidcName,
|
||||||
connectorFactoryName: oidcName,
|
connectorFactoryName: oidcName,
|
||||||
protocol: 'OIDC',
|
protocol: 'OIDC',
|
||||||
|
// Google Workspace connector have `issuer` predefined, for other OIDC-protocol-based connectors, we use Google's issuer to test the config fetcher and preview.
|
||||||
|
formData: { ...oidcFormData, issuer: 'https://accounts.google.com' },
|
||||||
|
previewResults: oidcPreviewResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
const samlName = 'SAML';
|
const samlName = 'SAML';
|
||||||
|
@ -40,6 +77,8 @@ const saml: SsoConnectorTestCase = {
|
||||||
connectorName: samlName,
|
connectorName: samlName,
|
||||||
connectorFactoryName: samlName,
|
connectorFactoryName: samlName,
|
||||||
protocol: 'SAML',
|
protocol: 'SAML',
|
||||||
|
formData: samlFormData,
|
||||||
|
previewResults: samlPreviewResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ssoConnectorTestCases: SsoConnectorTestCase[] = [
|
export const ssoConnectorTestCases: SsoConnectorTestCase[] = [
|
||||||
|
|
|
@ -62,7 +62,8 @@ describe('create SSO connectors', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each(ssoConnectorTestCases.slice(1))(
|
it.each(ssoConnectorTestCases.slice(1))(
|
||||||
'create other SSO connectors %p',
|
// The full object of `ssoConnector` test case is too burdensome for the test title, so we only use the index here.
|
||||||
|
'create other SSO connectors %#',
|
||||||
async (ssoConnector) => {
|
async (ssoConnector) => {
|
||||||
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||||
|
|
||||||
|
@ -100,13 +101,15 @@ describe('create SSO connectors', () => {
|
||||||
* To check only the `duplicated connector name` is blocked, even if the
|
* To check only the `duplicated connector name` is blocked, even if the
|
||||||
* existing SSO connector (with the occupied name) is created with a different connector factory.
|
* existing SSO connector (with the occupied name) is created with a different connector factory.
|
||||||
*/
|
*/
|
||||||
const { connectorFactoryName, protocol } = ssoConnectorTestCases[0]!;
|
const { connectorFactoryName, protocol, formData, previewResults } = ssoConnectorTestCases[0]!;
|
||||||
const { connectorName } = ssoConnectorTestCases[1]!;
|
const { connectorName } = ssoConnectorTestCases[1]!;
|
||||||
// Since the creation process is expected to be blocked in this test case, we do not want to check the connection info on details page.
|
// Since the creation process is expected to be blocked in this test case, we do not want to check the connection info on details page.
|
||||||
await fillSsoConnectorCreationModal(page, {
|
await fillSsoConnectorCreationModal(page, {
|
||||||
connectorFactoryName,
|
connectorFactoryName,
|
||||||
connectorName,
|
connectorName,
|
||||||
protocol,
|
protocol,
|
||||||
|
formData,
|
||||||
|
previewResults,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Error message should be shown.
|
// Error message should be shown.
|
||||||
|
|
Loading…
Add table
Reference in a new issue