Skip to content

Commit 33a7d70

Browse files
authored
test(e2e): add Enterprise SSO tests (#8092)
1 parent 206d2b7 commit 33a7d70

File tree

5 files changed

+104
-0
lines changed

5 files changed

+104
-0
lines changed

.changeset/light-queens-study.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

integration/.keys.json.sample

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,9 @@
6262
"with-protect-service": {
6363
"pk": "",
6464
"sk": ""
65+
},
66+
"with-enterprise-sso": {
67+
"pk": "",
68+
"sk": ""
6569
}
6670
}

integration/presets/envs.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ const withSharedUIVariant = withEmailCodes
119119

120120
const withEmailLinks = withInstanceKeys('with-email-links', base.clone().setId('withEmailLinks'));
121121

122+
const withEnterpriseSso = withInstanceKeys(
123+
'with-enterprise-sso',
124+
base
125+
.clone()
126+
.setId('withEnterpriseSso')
127+
.setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.E2E_CLERK_ENCRYPTION_KEY || 'a-key'),
128+
);
129+
122130
const withCustomRoles = withInstanceKeys(
123131
'with-custom-roles',
124132
base
@@ -252,6 +260,7 @@ export const envs = {
252260
withEmailCodesProxy,
253261
withEmailCodesQuickstart,
254262
withEmailLinks,
263+
withEnterpriseSso,
255264
withKeyless,
256265
withLegalConsent,
257266
withNeedsClientTrust,

integration/presets/longRunningApps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export const createLongRunningApps = () => {
7474
{ id: 'tanstack.react-start', config: tanstack.reactStart, env: envs.withEmailCodes },
7575
{ id: 'tanstack.react-start.withCustomRoles', config: tanstack.reactStart, env: envs.withCustomRoles },
7676
{ id: 'tanstack.react-start.withEmailCodesProxy', config: tanstack.reactStart, env: envs.withEmailCodesProxy },
77+
{ id: 'tanstack.react-start.withEnterpriseSso', config: tanstack.reactStart, env: envs.withEnterpriseSso },
7778

7879
/**
7980
* Various apps - basic flows
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type { EnterpriseConnection } from '@clerk/backend';
2+
import { expect, test } from '@playwright/test';
3+
4+
import { appConfigs } from '../../presets';
5+
import { createTestUtils, testAgainstRunningApps } from '../../testUtils';
6+
7+
// Self-signed certificate for the fake SAML IdP (required to activate enterprise connections)
8+
const FAKE_IDP_CERTIFICATE =
9+
'MIIDNzCCAh+gAwIBAgIUEWQRRTEkpHDPMS2f0JS+4L8yD2YwDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgZmFrZS1pZHAuZTJlLWVudGVycHJpc2UtdGVzdC5kZXYwHhcNMjYwMzE2MjIwNzMyWhcNMjcwMzE2MjIwNzMyWjArMSkwJwYDVQQDDCBmYWtlLWlkcC5lMmUtZW50ZXJwcmlzZS10ZXN0LmRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANIQpOAr5IaiOfx31RRcvQkejoMHldBbxF1hi9boiqqjhlZ+xvuWabmho5JDX5nIJkg31eOkfpFl1TBbMc6IvjvGLgFYinNlPZDArH3/WEw2hRD5f+FhHEBfaqSF+Ol/K4GtZ55lKtyMWI1Xv4avvGhRGbx1kKnMQAXayulmet49azGziJ7B7QwteZOuf6c1XxcQ/VFnIiIYQtN9cngA62pbv/InoZx762504HrlGtmDYxsoCmmDkTw/TXGi2p1X5OHETZV5UXI63mHLFlHdBXqvZDON5mt78p1iTAC1Bnnyd5b8CI6GVEzaMjXnMecKEV67w3HPdO9OcBCuFTqy7dcCAwEAAaNTMFEwHQYDVR0OBBYEFNJxwtOoHamUx+PKBexfDbAaazyVMB8GA1UdIwQYMBaAFNJxwtOoHamUx+PKBexfDbAaazyVMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG4PLtYjntt/cl3QitAAZBdygmp5sBkxvrS1lWVBBpgH/++hUZ9YEk8AeVi8bnpBKYUXMRTJvqzDoM+xxZEpmNtxm5rb5jp5Pz2mFmmORlD5nOGGB+xZI7BxLfqwjXdfb9zsB3b6nBdFkJKK85KpynNlsx1CfaEVyovTBxzELfW51o666DMCje07rdngckhQLwJ+Rxk3f2AGfjown/TSa/v6Cz7ZK51fpiQwAI+JIwElohmhB8pwghw45+nknSWV7rggbmejJM/RoAKZDNYGt48X3VrnvWSoGfOL9ny/xf1AJ+bdlEheOpigtMq9dE81b0EigWJ8luLHGT5wKaKrqtk=';
10+
11+
/**
12+
* Helper to create and activate a SAML enterprise connection.
13+
* The Clerk API requires creating the connection first (inactive), then activating via update.
14+
* The `provider` field is required by the API but missing from the SDK types, so we cast.
15+
*/
16+
async function createActiveEnterpriseConnection(
17+
clerk: ReturnType<typeof createTestUtils>['services']['clerk'],
18+
opts: { name: string; domain: string; idpEntityId: string; idpSsoUrl: string },
19+
): Promise<EnterpriseConnection> {
20+
const conn = await clerk.enterpriseConnections.createEnterpriseConnection({
21+
name: opts.name,
22+
domains: [opts.domain],
23+
provider: 'saml_custom',
24+
saml: {
25+
idpEntityId: opts.idpEntityId,
26+
idpSsoUrl: opts.idpSsoUrl,
27+
idpCertificate: FAKE_IDP_CERTIFICATE,
28+
},
29+
} as Parameters<typeof clerk.enterpriseConnections.createEnterpriseConnection>[0]);
30+
31+
return clerk.enterpriseConnections.updateEnterpriseConnection(conn.id, { active: true });
32+
}
33+
34+
testAgainstRunningApps({ withEnv: [appConfigs.envs.withEnterpriseSso] })(
35+
'enterprise SSO tests for @tanstack-react-start',
36+
({ app }) => {
37+
test.describe.configure({ mode: 'serial' });
38+
39+
const testDomain = 'e2e-enterprise-test.dev';
40+
const fakeIdpHost = `fake-idp.${testDomain}`;
41+
let enterpriseConnection: EnterpriseConnection;
42+
43+
test.beforeAll(async () => {
44+
const u = createTestUtils({ app });
45+
enterpriseConnection = await createActiveEnterpriseConnection(u.services.clerk, {
46+
name: 'E2E Test SAML Connection',
47+
domain: testDomain,
48+
idpEntityId: `https://${fakeIdpHost}`,
49+
idpSsoUrl: `https://${fakeIdpHost}/sso`,
50+
});
51+
});
52+
53+
test.afterAll(async () => {
54+
const u = createTestUtils({ app });
55+
await u.services.clerk.enterpriseConnections.deleteEnterpriseConnection(enterpriseConnection.id);
56+
await app.teardown();
57+
});
58+
59+
test('sign-in with enterprise domain email initiates SSO redirect', async ({ page, context }) => {
60+
const u = createTestUtils({ app, page, context });
61+
62+
// Capture the redirect to the fake IdP (proves enterprise SSO kicked in)
63+
const idpRequestPromise = page.waitForRequest(req => req.url().includes(fakeIdpHost));
64+
65+
await u.po.signIn.goTo();
66+
await u.po.signIn.setIdentifier(`testuser@${testDomain}`);
67+
await u.po.signIn.continue();
68+
69+
// Verify the browser was redirected to the enterprise IdP
70+
const idpRequest = await idpRequestPromise;
71+
expect(idpRequest.url()).toContain(fakeIdpHost);
72+
});
73+
74+
test('non-managed domain email does not trigger SSO redirect', async ({ page, context }) => {
75+
const u = createTestUtils({ app, page, context });
76+
77+
await u.po.signIn.goTo();
78+
await u.po.signIn.setIdentifier('[email protected]');
79+
await u.po.signIn.continue();
80+
81+
// The sign-in form should remain visible (no redirect to an IdP)
82+
await u.po.signIn.waitForMounted();
83+
84+
// URL should still be on the app's sign-in page, not redirected externally
85+
expect(page.url()).toContain('/sign-in');
86+
});
87+
},
88+
);

0 commit comments

Comments
 (0)