0
Fork 0
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:
Darcy Ye 2024-01-11 17:26:35 +08:00 committed by GitHub
parent 2f0e2a4400
commit 4a517379b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 4 deletions

View file

@ -1,6 +1,7 @@
import { conditionalString } from '@silverhand/essentials';
import { conditional, conditionalString } from '@silverhand/essentials';
import { type Page } from 'puppeteer';
import { expectToSaveChanges, waitForToast } from '#src/ui-helpers/index.js';
import { dcls, cls } from '#src/utils.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) => {
return page.waitForSelector(
`.ReactModalPortal div[class$=footer] button${conditionalString(
@ -78,7 +138,7 @@ export const findModalFooterButton = async (isButtonDisabled = false) => {
export const fillSsoConnectorCreationModal = async (
page: Page,
{ connectorFactoryName, connectorName, protocol }: SsoConnectorTestCase,
{ connectorFactoryName, connectorName, protocol, formData, previewResults }: SsoConnectorTestCase,
checkConnectionInfo = false
) => {
// Button should be disabled util form is filled.
@ -114,5 +174,7 @@ export const fillSsoConnectorCreationModal = async (
await page.waitForNavigation({ waitUntil: 'networkidle0' });
await checkSsoConnectorConnectionTabInfo(page, protocol);
await configureSsoConnectorConnection(page, formData, protocol, previewResults);
}
};

View file

@ -5,6 +5,33 @@ export type SsoConnectorTestCase = {
connectorName: string;
connectorFactoryName: string;
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';
@ -12,6 +39,8 @@ const microsoftEntraID: SsoConnectorTestCase = {
connectorName: microsoftEntraIdName,
connectorFactoryName: microsoftEntraIdName,
protocol: 'SAML',
formData: samlFormData,
previewResults: samlPreviewResults,
};
const googleWorkspaceName = 'Google Workspace';
@ -19,6 +48,8 @@ const googleWorkspace: SsoConnectorTestCase = {
connectorName: googleWorkspaceName,
connectorFactoryName: googleWorkspaceName,
protocol: 'OIDC',
formData: oidcFormData,
previewResults: oidcPreviewResults,
};
const oktaName = 'Okta';
@ -26,6 +57,9 @@ const okta: SsoConnectorTestCase = {
connectorName: oktaName,
connectorFactoryName: oktaName,
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';
@ -33,6 +67,9 @@ const oidc: SsoConnectorTestCase = {
connectorName: oidcName,
connectorFactoryName: oidcName,
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';
@ -40,6 +77,8 @@ const saml: SsoConnectorTestCase = {
connectorName: samlName,
connectorFactoryName: samlName,
protocol: 'SAML',
formData: samlFormData,
previewResults: samlPreviewResults,
};
export const ssoConnectorTestCases: SsoConnectorTestCase[] = [

View file

@ -62,7 +62,8 @@ describe('create SSO connectors', () => {
});
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) => {
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
* 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]!;
// 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, {
connectorFactoryName,
connectorName,
protocol,
formData,
previewResults,
});
// Error message should be shown.