diff --git a/.changeset/sharp-glasses-clap.md b/.changeset/sharp-glasses-clap.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/sharp-glasses-clap.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/integration/presets/envs.ts b/integration/presets/envs.ts index 21b1794e7b..200df9927d 100644 --- a/integration/presets/envs.ts +++ b/integration/presets/envs.ts @@ -88,6 +88,12 @@ const withDynamicKeys = withEmailCodes .setEnvVariable('private', 'CLERK_SECRET_KEY', '') .setEnvVariable('private', 'CLERK_DYNAMIC_SECRET_KEY', instanceKeys.get('with-email-codes').sk); +const withRestrictedMode = withEmailCodes + .clone() + .setId('withRestrictedMode') + .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-restricted-mode').sk) + .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-restricted-mode').pk); + export const envs = { base, withEmailCodes, @@ -100,4 +106,5 @@ export const envs = { withAPCore2ClerkLatest, withAPCore2ClerkV4, withDynamicKeys, + withRestrictedMode, } as const; diff --git a/integration/testUtils/index.ts b/integration/testUtils/index.ts index 75f8f77801..2105800614 100644 --- a/integration/testUtils/index.ts +++ b/integration/testUtils/index.ts @@ -5,6 +5,7 @@ import { expect } from '@playwright/test'; import type { Application } from '../models/application'; import { createAppPageObject } from './appPageObject'; import { createEmailService } from './emailService'; +import { createInvitationService } from './invitationsService'; import { createOrganizationSwitcherComponentPageObject } from './organizationSwitcherPageObject'; import type { EnchancedPage, TestArgs } from './signInPageObject'; import { createSignInComponentPageObject } from './signInPageObject'; @@ -73,6 +74,7 @@ export const createTestUtils = < const services = { email: createEmailService(), users: createUserService(clerkClient), + invitations: createInvitationService(clerkClient), clerk: clerkClient, }; diff --git a/integration/testUtils/invitationsService.ts b/integration/testUtils/invitationsService.ts new file mode 100644 index 0000000000..b8f87e94e5 --- /dev/null +++ b/integration/testUtils/invitationsService.ts @@ -0,0 +1,16 @@ +import type { ClerkClient, Invitation } from '@clerk/backend'; + +export type InvitationService = { + createBapiInvitation: (emailAddress: string) => Promise; +}; +export const createInvitationService = (clerkClient: ClerkClient) => { + const self: InvitationService = { + createBapiInvitation: async emailAddress => { + return await clerkClient.invitations.createInvitation({ + emailAddress, + }); + }, + }; + + return self; +}; diff --git a/integration/tests/restricted-mode.test.ts b/integration/tests/restricted-mode.test.ts new file mode 100644 index 0000000000..ab01612663 --- /dev/null +++ b/integration/tests/restricted-mode.test.ts @@ -0,0 +1,131 @@ +import { expect, test } from '@playwright/test'; + +import type { Application } from '../models/application'; +import { appConfigs } from '../presets'; +import type { FakeUser } from '../testUtils'; +import { createTestUtils } from '../testUtils'; + +test.describe('Sign-up restricted mode', () => { + test.describe.configure({ mode: 'serial' }); + let app: Application; + let fakeUser: FakeUser; + + test.beforeAll(async () => { + app = await appConfigs.next.appRouter + .clone() + .addFile( + 'src/app/provider.tsx', + () => `'use client' +import { ClerkProvider } from "@clerk/nextjs"; + +export function Provider({ children }: { children: any }) { + return ( + + {children} + + ) +}`, + ) + .addFile( + 'src/app/layout.tsx', + () => `import './globals.css'; +import { Inter } from 'next/font/google'; +import { Provider } from './provider'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +}`, + ) + .addFile( + 'src/app/hash/user/page.tsx', + () => ` +import { UserProfile, UserButton } from '@clerk/nextjs'; + +export default function Page() { + return ( +
+ + +
+ ); +}`, + ) + .commit(); + await app.setup(); + await app.withEnv(appConfigs.envs.withRestrictedMode); + await app.dev(); + + const m = createTestUtils({ app }); + fakeUser = m.services.users.createFakeUser({ + withUsername: true, + fictionalEmail: true, + withPhoneNumber: true, + }); + await m.services.users.createBapiUser({ + ...fakeUser, + username: undefined, + phoneNumber: undefined, + }); + }); + + test.afterAll(async () => { + await fakeUser.deleteIfExists(); + await app.teardown(); + }); + + test('Existing user signs in succesfull', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo(); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.expect.toBeSignedIn(); + + await u.po.userProfile.goTo(); + await u.po.userProfile.waitForMounted(); + }); + + test('Sign up page return restricted and click Back to sign in', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signUp.goTo(); + await u.po.signUp.waitForMounted(); + + await expect(u.page.getByText(/Restricted access/i).first()).toBeVisible(); + const backToSignIn = u.page.getByRole('link', { name: /Back to sign in/i }); + await backToSignIn.click(); + + await u.po.signUp.waitForMounted(); + await u.page.waitForAppUrl('/sign-up'); + }); + + test('Sign up page with invitation render correctly and sign up', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + const invitedUser = u.services.users.createFakeUser(); + + const invitation = await u.services.invitations.createBapiInvitation(invitedUser.email); + + await u.page.goto(invitation.url); + await u.po.signUp.waitForMounted(); + await expect(u.page.getByText(/Create your account/i).first()).toBeVisible(); + + await u.po.signUp.signUp({ + password: invitedUser.password, + }); + + await u.po.expect.toBeSignedIn(); + + await invitedUser.deleteIfExists(); + }); +});