From 3861bcac36e2411225869bc92254289de9e2bf02 Mon Sep 17 00:00:00 2001 From: Darcy Ye Date: Tue, 2 Jan 2024 11:37:57 +0800 Subject: [PATCH] chore(test): add test cases for SSO connection info (#5092) --- .../tests/console/sso-connectors/helpers.ts | 86 ++++++++++++++++++- .../sso-connectors-test-cases.ts | 8 ++ .../sso-connectors/sso-connectors.test.ts | 12 ++- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/packages/integration-tests/src/tests/console/sso-connectors/helpers.ts b/packages/integration-tests/src/tests/console/sso-connectors/helpers.ts index d0c6152ab..afef62594 100644 --- a/packages/integration-tests/src/tests/console/sso-connectors/helpers.ts +++ b/packages/integration-tests/src/tests/console/sso-connectors/helpers.ts @@ -1,7 +1,72 @@ import { conditionalString } from '@silverhand/essentials'; import { type Page } from 'puppeteer'; -import { type SsoConnectorTestCase } from './sso-connectors-test-cases.js'; +import { dcls, cls } from '#src/utils.js'; + +import { type SsoConnectorTestCase, type Protocol } from './sso-connectors-test-cases.js'; + +const getAndCheckValueByFieldName = async (page: Page, fieldName: string, expectSuffix: string) => { + const valueField = await expect(page).toMatchElement( + [dcls('form'), `${dcls('field')}:has(${dcls('headline')} > ${dcls('title')})`].join(' '), + { text: fieldName } + ); + const value = await valueField.$eval( + [dcls('copyToClipboard'), dcls('row'), dcls('content')].join(' '), + (element) => element.textContent + ); + + expect(value?.endsWith(expectSuffix)).toBeTruthy(); +}; + +// Check the correctness of automatically generated connection info on the `Connection` tab. +const checkSsoConnectorConnectionTabInfo = async (page: Page, protocol: Protocol) => { + // Wait for the details page redirect to default tab (which is `Connection` tab). + await page.waitForNavigation({ waitUntil: 'networkidle0' }); + + // eslint-disable-next-line prefer-regex-literals + const regExpForDetailsPageUrl = new RegExp('enterprise-sso\\/([^/]+)\\/connection'); + + expect(regExpForDetailsPageUrl.test(page.url())).toBe(true); + const ssoConnectorIdFromUrl = regExpForDetailsPageUrl.exec(page.url())?.[1]; + + if (!ssoConnectorIdFromUrl) { + throw new Error('SSO connector ID is not found in URL.'); + } + + // The SSO connector ID shown on the page should match the ID of the SSO connector in URL. + await expect(page).toMatchElement( + [ + dcls('metadata'), + dcls('row'), + `${dcls('container')}${cls('copyId')}`, + dcls('row'), + dcls('content'), + ].join(' '), + { text: ssoConnectorIdFromUrl } + ); + + if (protocol === 'SAML') { + await getAndCheckValueByFieldName( + page, + 'Assertion consumer service URL (Reply URL)', + `api/authn/single-sign-on/saml/${ssoConnectorIdFromUrl}` + ); + + await getAndCheckValueByFieldName( + page, + 'Audience URI (SP Entity ID)', + `enterprise-sso/${ssoConnectorIdFromUrl}` + ); + } + + if (protocol === 'OIDC') { + await getAndCheckValueByFieldName( + page, + 'Redirect URI (Callback URL)', + `callback/${ssoConnectorIdFromUrl}` + ); + } +}; export const findModalFooterButton = async (isButtonDisabled = false) => { return page.waitForSelector( @@ -13,14 +78,22 @@ export const findModalFooterButton = async (isButtonDisabled = false) => { export const fillSsoConnectorCreationModal = async ( page: Page, - { connectorFactoryName, connectorName }: SsoConnectorTestCase + { connectorFactoryName, connectorName, protocol }: SsoConnectorTestCase, + checkConnectionInfo = false ) => { // Button should be disabled util form is filled. await expect(findModalFooterButton(true)).resolves.toBeTruthy(); // Select connector factory await expect(page).toClick( - `.ReactModalPortal div[role=radio] div[class$=ssoConnector] div[class$=content] div[class$=name] span`, + [ + '.ReactModalPortal', + 'div[role=radio]', + dcls('ssoConnector'), + dcls('content'), + dcls('name'), + 'span', + ].join(' '), { text: connectorFactoryName } ); @@ -35,4 +108,11 @@ export const fillSsoConnectorCreationModal = async ( // Button should enabled. const createButton = await findModalFooterButton(); await createButton?.click(); + + if (checkConnectionInfo) { + // Wait for the page redirect to details page. + await page.waitForNavigation({ waitUntil: 'networkidle0' }); + + await checkSsoConnectorConnectionTabInfo(page, protocol); + } }; diff --git a/packages/integration-tests/src/tests/console/sso-connectors/sso-connectors-test-cases.ts b/packages/integration-tests/src/tests/console/sso-connectors/sso-connectors-test-cases.ts index 738bd7a4c..cd2f63ad2 100644 --- a/packages/integration-tests/src/tests/console/sso-connectors/sso-connectors-test-cases.ts +++ b/packages/integration-tests/src/tests/console/sso-connectors/sso-connectors-test-cases.ts @@ -1,37 +1,45 @@ // Will extend this type definition later since we are going to configure the SSO connectors with specific values. +export type Protocol = 'SAML' | 'OIDC'; + export type SsoConnectorTestCase = { connectorName: string; connectorFactoryName: string; + protocol: Protocol; }; const microsoftEntraIdName = 'Microsoft Entra ID'; const microsoftEntraID: SsoConnectorTestCase = { connectorName: microsoftEntraIdName, connectorFactoryName: microsoftEntraIdName, + protocol: 'SAML', }; const googleWorkspaceName = 'Google Workspace'; const googleWorkspace: SsoConnectorTestCase = { connectorName: googleWorkspaceName, connectorFactoryName: googleWorkspaceName, + protocol: 'OIDC', }; const oktaName = 'Okta'; const okta: SsoConnectorTestCase = { connectorName: oktaName, connectorFactoryName: oktaName, + protocol: 'OIDC', }; const oidcName = 'OIDC'; const oidc: SsoConnectorTestCase = { connectorName: oidcName, connectorFactoryName: oidcName, + protocol: 'OIDC', }; const samlName = 'SAML'; const saml: SsoConnectorTestCase = { connectorName: samlName, connectorFactoryName: samlName, + protocol: 'SAML', }; export const ssoConnectorTestCases: SsoConnectorTestCase[] = [ diff --git a/packages/integration-tests/src/tests/console/sso-connectors/sso-connectors.test.ts b/packages/integration-tests/src/tests/console/sso-connectors/sso-connectors.test.ts index 419ee5498..d65446013 100644 --- a/packages/integration-tests/src/tests/console/sso-connectors/sso-connectors.test.ts +++ b/packages/integration-tests/src/tests/console/sso-connectors/sso-connectors.test.ts @@ -55,9 +55,7 @@ describe('create SSO connectors', () => { await expectModalWithTitle(page, 'Add enterprise connector'); - await fillSsoConnectorCreationModal(page, ssoConnectorTestCases[0]!); - - await page.waitForNavigation({ waitUntil: 'networkidle0' }); + await fillSsoConnectorCreationModal(page, ssoConnectorTestCases[0]!, true); // Come back to Enterprise SSO listing page. await page.goto(appendPathname('/console/enterprise-sso', logtoConsoleUrl).href); @@ -78,9 +76,7 @@ describe('create SSO connectors', () => { await expectModalWithTitle(page, 'Add enterprise connector'); - await fillSsoConnectorCreationModal(page, ssoConnector); - - await page.waitForNavigation({ waitUntil: 'networkidle0' }); + await fillSsoConnectorCreationModal(page, ssoConnector, true); // Come back to Enterprise SSO listing page. await page.goto(appendPathname('/console/enterprise-sso', logtoConsoleUrl).href); @@ -104,11 +100,13 @@ 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 } = ssoConnectorTestCases[0]!; + const { connectorFactoryName, protocol } = 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, }); // Error message should be shown.