From 15878a4ac559f1106d0fa7ab9d0b8dba13928a85 Mon Sep 17 00:00:00 2001 From: victorvhs017 <115753265+victorvhs017@users.noreply.github.com> Date: Mon, 7 Apr 2025 03:07:39 -0300 Subject: [PATCH] chore: Refactored the Turnstile next public env variable and added test files (#4997) Co-authored-by: Piyush Gupta --- .env.example | 2 +- .../components/PosthogIdentify.test.tsx | 2 - .../(organization)/general/page.test.tsx | 16 +- apps/web/app/sentry/SentryProvider.test.tsx | 2 +- .../signup/components/signup-form.test.tsx | 377 ++++++++++++++++++ .../auth/signup/components/signup-form.tsx | 16 +- apps/web/modules/auth/signup/page.test.tsx | 67 +++- apps/web/modules/auth/signup/page.tsx | 2 + .../(fresh-instance)/signup/page.test.tsx | 97 +++++ .../setup/(fresh-instance)/signup/page.tsx | 2 + apps/web/vite.config.mts | 7 +- docker/docker-compose.yml | 2 +- packages/lib/constants.ts | 15 +- packages/lib/env.ts | 4 +- turbo.json | 2 +- 15 files changed, 567 insertions(+), 46 deletions(-) create mode 100644 apps/web/modules/auth/signup/components/signup-form.test.tsx create mode 100644 apps/web/modules/setup/(fresh-instance)/signup/page.test.tsx diff --git a/.env.example b/.env.example index 8d66de1e91..4677a33dcc 100644 --- a/.env.example +++ b/.env.example @@ -117,7 +117,7 @@ IMPRINT_URL= IMPRINT_ADDRESS= # Configure Turnstile in signup flow -# NEXT_PUBLIC_TURNSTILE_SITE_KEY= +# TURNSTILE_SITE_KEY= # TURNSTILE_SECRET_KEY= # Configure Github Login diff --git a/apps/web/app/(app)/environments/[environmentId]/components/PosthogIdentify.test.tsx b/apps/web/app/(app)/environments/[environmentId]/components/PosthogIdentify.test.tsx index 90eb344a89..0861c570f7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/components/PosthogIdentify.test.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/components/PosthogIdentify.test.tsx @@ -1,9 +1,7 @@ -// PosthogIdentify.test.tsx import "@testing-library/jest-dom/vitest"; import { cleanup, render } from "@testing-library/react"; import { Session } from "next-auth"; import { usePostHog } from "posthog-js/react"; -import React from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { TOrganizationBilling } from "@formbricks/types/organizations"; import { TUser } from "@formbricks/types/user"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.test.tsx index b31e6432d6..7b097c666b 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.test.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.test.tsx @@ -33,12 +33,16 @@ vi.mock("@formbricks/lib/constants", () => ({ WEBAPP_URL: "mock-webapp-url", SMTP_HOST: "mock-smtp-host", SMTP_PORT: "mock-smtp-port", - AI_AZURE_LLM_RESSOURCE_NAME: "mock-azure-llm-resource-name", - AI_AZURE_LLM_API_KEY: "mock-azure-llm-api-key", - AI_AZURE_LLM_DEPLOYMENT_ID: "mock-azure-llm-deployment-id", - AI_AZURE_EMBEDDINGS_RESSOURCE_NAME: "mock-azure-embeddings-resource-name", - AI_AZURE_EMBEDDINGS_API_KEY: "mock-azure-embeddings-api-key", - AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID: "mock-azure-embeddings-deployment-id", + AI_AZURE_LLM_RESSOURCE_NAME: "mock-ai-azure-llm-ressource-name", + AI_AZURE_LLM_API_KEY: "mock-ai", + AI_AZURE_LLM_DEPLOYMENT_ID: "mock-ai-azure-llm-deployment-id", + AI_AZURE_EMBEDDINGS_RESSOURCE_NAME: "mock-ai-azure-embeddings-ressource-name", + AI_AZURE_EMBEDDINGS_API_KEY: "mock-ai-azure-embeddings-api-key", + AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID: "mock-ai-azure-embeddings-deployment-id", +})); + +vi.mock("next-auth", () => ({ + getServerSession: vi.fn(), })); vi.mock("@/tolgee/server", () => ({ diff --git a/apps/web/app/sentry/SentryProvider.test.tsx b/apps/web/app/sentry/SentryProvider.test.tsx index 40b58e7165..89a44fa396 100644 --- a/apps/web/app/sentry/SentryProvider.test.tsx +++ b/apps/web/app/sentry/SentryProvider.test.tsx @@ -1,6 +1,6 @@ import * as Sentry from "@sentry/nextjs"; import { cleanup, render, screen } from "@testing-library/react"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { SentryProvider } from "./SentryProvider"; vi.mock("@sentry/nextjs", async () => { diff --git a/apps/web/modules/auth/signup/components/signup-form.test.tsx b/apps/web/modules/auth/signup/components/signup-form.test.tsx new file mode 100644 index 0000000000..e494668f75 --- /dev/null +++ b/apps/web/modules/auth/signup/components/signup-form.test.tsx @@ -0,0 +1,377 @@ +import { getFormattedErrorMessage } from "@/lib/utils/helper"; +import { createUserAction } from "@/modules/auth/signup/actions"; +import "@testing-library/jest-dom/vitest"; +import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { useSearchParams } from "next/navigation"; +import toast from "react-hot-toast"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { createEmailTokenAction } from "../../../auth/actions"; +import { SignupForm } from "./signup-form"; + +// Mock dependencies + +vi.mock("@formbricks/lib/constants", () => ({ + IS_FORMBRICKS_CLOUD: false, + POSTHOG_API_KEY: "mock-posthog-api-key", + POSTHOG_HOST: "mock-posthog-host", + IS_POSTHOG_CONFIGURED: true, + ENCRYPTION_KEY: "mock-encryption-key", + ENTERPRISE_LICENSE_KEY: "mock-enterprise-license-key", + GITHUB_ID: "mock-github-id", + GITHUB_SECRET: "test-githubID", + GOOGLE_CLIENT_ID: "test-google-client-id", + GOOGLE_CLIENT_SECRET: "test-google-client-secret", + AZUREAD_CLIENT_ID: "test-azuread-client-id", + AZUREAD_CLIENT_SECRET: "test-azure", + AZUREAD_TENANT_ID: "test-azuread-tenant-id", + OIDC_DISPLAY_NAME: "test-oidc-display-name", + OIDC_CLIENT_ID: "test-oidc-client-id", + OIDC_ISSUER: "test-oidc-issuer", + OIDC_CLIENT_SECRET: "test-oidc-client-secret", + OIDC_SIGNING_ALGORITHM: "test-oidc-signing-algorithm", + WEBAPP_URL: "test-webapp-url", + IS_PRODUCTION: false, + SENTRY_DSN: "mock-sentry-dsn", + FB_LOGO_URL: "mock-fb-logo-url", + SMTP_HOST: "smtp.example.com", + SMTP_PORT: 587, + SMTP_USER: "smtp-user", +})); + +// Set up a push mock for useRouter +const pushMock = vi.fn(); +vi.mock("next/navigation", () => ({ + useRouter: () => ({ + push: pushMock, + }), + useSearchParams: vi.fn(), +})); + +vi.mock("react-turnstile", () => ({ + useTurnstile: () => ({ + reset: vi.fn(), + }), + default: (props: any) => ( +
{ + if (props.onSuccess) { + props.onSuccess("test-turnstile-token"); + } + }} + {...props} + /> + ), +})); + +vi.mock("react-hot-toast", () => ({ + default: { + error: vi.fn(), + toast: { + error: vi.fn(), + }, + }, +})); + +vi.mock("@/modules/auth/signup/actions", () => ({ + createUserAction: vi.fn(), +})); + +vi.mock("../../../auth/actions", () => ({ + createEmailTokenAction: vi.fn(), +})); + +vi.mock("@/lib/utils/helper", () => ({ + getFormattedErrorMessage: vi.fn(), +})); + +// Mock components + +vi.mock("@/modules/ee/sso/components/sso-options", () => ({ + SSOOptions: () =>
SSOOptions
, +})); +vi.mock("@/modules/auth/signup/components/terms-privacy-links", () => ({ + TermsPrivacyLinks: () =>
TermsPrivacyLinks
, +})); +vi.mock("@/modules/ui/components/button", () => ({ + Button: (props: any) => , +})); +vi.mock("@/modules/ui/components/input", () => ({ + Input: (props: any) => , +})); +vi.mock("@/modules/ui/components/password-input", () => ({ + PasswordInput: (props: any) => , +})); + +const defaultProps = { + webAppUrl: "http://localhost", + privacyUrl: "http://localhost/privacy", + termsUrl: "http://localhost/terms", + emailAuthEnabled: true, + googleOAuthEnabled: false, + githubOAuthEnabled: false, + azureOAuthEnabled: false, + oidcOAuthEnabled: false, + userLocale: "en-US", + emailVerificationDisabled: false, + isSsoEnabled: false, + samlSsoEnabled: false, + isTurnstileConfigured: false, + samlTenant: "", + samlProduct: "", + defaultOrganizationId: "org1", + defaultOrganizationRole: "member", + turnstileSiteKey: "dummy", // not used since isTurnstileConfigured is false +} as const; + +describe("SignupForm", () => { + afterEach(() => { + cleanup(); + }); + + it("toggles the signup form on button click", () => { + render(); + + // Initially, the signup form is hidden. + try { + screen.getByTestId("signup-name"); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } + + // Click the button to reveal the signup form. + const toggleButton = screen.getByTestId("signup-show-login"); + fireEvent.click(toggleButton); + + // Now the input fields should appear. + expect(screen.getByTestId("signup-name")).toBeInTheDocument(); + expect(screen.getByTestId("signup-email")).toBeInTheDocument(); + expect(screen.getByTestId("signup-password")).toBeInTheDocument(); + }); + + it("submits the form successfully", async () => { + // Set up mocks for the API actions. + vi.mocked(createUserAction).mockResolvedValue({ data: true } as any); + vi.mocked(createEmailTokenAction).mockResolvedValue({ data: "token123" }); + + render(); + + // Click the button to reveal the signup form. + const toggleButton = screen.getByTestId("signup-show-login"); + fireEvent.click(toggleButton); + + const nameInput = screen.getByTestId("signup-name"); + const emailInput = screen.getByTestId("signup-email"); + const passwordInput = screen.getByTestId("signup-password"); + + fireEvent.change(nameInput, { target: { value: "Test User" } }); + fireEvent.change(emailInput, { target: { value: "test@example.com" } }); + fireEvent.change(passwordInput, { target: { value: "Password123" } }); + + const submitButton = screen.getByTestId("signup-submit"); + fireEvent.submit(submitButton); + + await waitFor(() => { + expect(createUserAction).toHaveBeenCalledWith({ + name: "Test User", + email: "test@example.com", + password: "Password123", + userLocale: defaultProps.userLocale, + inviteToken: "", + emailVerificationDisabled: defaultProps.emailVerificationDisabled, + defaultOrganizationId: defaultProps.defaultOrganizationId, + defaultOrganizationRole: defaultProps.defaultOrganizationRole, + turnstileToken: undefined, + }); + }); + + await waitFor(() => { + expect(createEmailTokenAction).toHaveBeenCalledWith({ email: "test@example.com" }); + }); + + // Since email verification is enabled (emailVerificationDisabled is false), + // router.push should be called with the verification URL. + expect(pushMock).toHaveBeenCalledWith("/auth/verification-requested?token=token123"); + }); + + it("submits the form successfully when turnstile is configured", async () => { + // Override props to enable Turnstile + const props = { + ...defaultProps, + isTurnstileConfigured: true, + turnstileSiteKey: "dummy", + emailVerificationDisabled: true, + }; + + // Set up mocks for the API actions + vi.mocked(createUserAction).mockResolvedValue({ data: true } as any); + vi.mocked(createEmailTokenAction).mockResolvedValue({ data: "token123" }); + + render(); + + // Click the button to reveal the signup form + const toggleButton = screen.getByTestId("signup-show-login"); + fireEvent.click(toggleButton); + + // Fill out the form fields + fireEvent.change(screen.getByTestId("signup-name"), { target: { value: "Test User" } }); + fireEvent.change(screen.getByTestId("signup-email"), { target: { value: "test@example.com" } }); + fireEvent.change(screen.getByTestId("signup-password"), { target: { value: "Password123" } }); + + // Simulate receiving a turnstile token by clicking the Turnstile element. + const turnstileElement = screen.getByTestId("turnstile"); + fireEvent.click(turnstileElement); + + // Submit the form. + const submitButton = screen.getByTestId("signup-submit"); + fireEvent.submit(submitButton); + await waitFor(() => { + expect(createUserAction).toHaveBeenCalledWith({ + name: "Test User", + email: "test@example.com", + password: "Password123", + userLocale: props.userLocale, + inviteToken: "", + emailVerificationDisabled: true, + defaultOrganizationId: props.defaultOrganizationId, + defaultOrganizationRole: props.defaultOrganizationRole, + turnstileToken: "test-turnstile-token", + }); + }); + + await waitFor(() => { + expect(createEmailTokenAction).toHaveBeenCalledWith({ email: "test@example.com" }); + }); + + expect(pushMock).toHaveBeenCalledWith("/auth/signup-without-verification-success"); + }); + + it("submits the form successfully when turnstile is configured, but createEmailTokenAction don't return data", async () => { + // Override props to enable Turnstile + const props = { + ...defaultProps, + isTurnstileConfigured: true, + turnstileSiteKey: "dummy", + emailVerificationDisabled: true, + }; + + // Set up mocks for the API actions + vi.mocked(createUserAction).mockResolvedValue({ data: true } as any); + vi.mocked(createEmailTokenAction).mockResolvedValue(undefined); + vi.mocked(getFormattedErrorMessage).mockReturnValue("error"); + + render(); + + // Click the button to reveal the signup form + const toggleButton = screen.getByTestId("signup-show-login"); + fireEvent.click(toggleButton); + + // Fill out the form fields + fireEvent.change(screen.getByTestId("signup-name"), { target: { value: "Test User" } }); + fireEvent.change(screen.getByTestId("signup-email"), { target: { value: "test@example.com" } }); + fireEvent.change(screen.getByTestId("signup-password"), { target: { value: "Password123" } }); + + // Simulate receiving a turnstile token by clicking the Turnstile element. + const turnstileElement = screen.getByTestId("turnstile"); + fireEvent.click(turnstileElement); + + // Submit the form. + const submitButton = screen.getByTestId("signup-submit"); + fireEvent.submit(submitButton); + await waitFor(() => { + expect(createUserAction).toHaveBeenCalledWith({ + name: "Test User", + email: "test@example.com", + password: "Password123", + userLocale: props.userLocale, + inviteToken: "", + emailVerificationDisabled: true, + defaultOrganizationId: props.defaultOrganizationId, + defaultOrganizationRole: props.defaultOrganizationRole, + turnstileToken: "test-turnstile-token", + }); + }); + + // Since Turnstile is configured, but no token is received, an error message should be shown. + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith("error"); + }); + }); + + it("shows an error message if turnstile is configured, but no token is received", async () => { + // Override props to enable Turnstile + const props = { + ...defaultProps, + isTurnstileConfigured: true, + turnstileSiteKey: "dummy", + emailVerificationDisabled: true, + }; + + // Set up mocks for the API actions + vi.mocked(createUserAction).mockResolvedValue({ data: true } as any); + vi.mocked(createEmailTokenAction).mockResolvedValue({ data: "token123" }); + + render(); + + // Click the button to reveal the signup form + const toggleButton = screen.getByTestId("signup-show-login"); + fireEvent.click(toggleButton); + + // Fill out the form fields + fireEvent.change(screen.getByTestId("signup-name"), { target: { value: "Test User" } }); + fireEvent.change(screen.getByTestId("signup-email"), { target: { value: "test@example.com" } }); + fireEvent.change(screen.getByTestId("signup-password"), { target: { value: "Password123" } }); + + // Submit the form. + const submitButton = screen.getByTestId("signup-submit"); + fireEvent.submit(submitButton); + + // Since Turnstile is configured, but no token is received, an error message should be shown. + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith("auth.signup.please_verify_captcha"); + }); + }); + + it("Invite token is in the search params", async () => { + // Set up mocks for the API actions + vi.mocked(createUserAction).mockResolvedValue({ data: true } as any); + vi.mocked(createEmailTokenAction).mockResolvedValue({ data: "token123" }); + vi.mocked(useSearchParams).mockReturnValue(new URLSearchParams("inviteToken=token123") as any); + + render(); + + // Click the button to reveal the signup form + const toggleButton = screen.getByTestId("signup-show-login"); + fireEvent.click(toggleButton); + + // Fill out the form fields + fireEvent.change(screen.getByTestId("signup-name"), { target: { value: "Test User" } }); + fireEvent.change(screen.getByTestId("signup-email"), { target: { value: "test@example.com" } }); + fireEvent.change(screen.getByTestId("signup-password"), { target: { value: "Password123" } }); + + // Submit the form. + const submitButton = screen.getByTestId("signup-submit"); + fireEvent.submit(submitButton); + + // Check that the invite token is passed to the createUserAction + await waitFor(() => { + expect(createUserAction).toHaveBeenCalledWith({ + name: "Test User", + email: "test@example.com", + password: "Password123", + userLocale: defaultProps.userLocale, + inviteToken: "token123", + emailVerificationDisabled: defaultProps.emailVerificationDisabled, + defaultOrganizationId: defaultProps.defaultOrganizationId, + defaultOrganizationRole: defaultProps.defaultOrganizationRole, + turnstileToken: undefined, + }); + }); + + await waitFor(() => { + expect(createEmailTokenAction).toHaveBeenCalledWith({ email: "test@example.com" }); + }); + + expect(pushMock).toHaveBeenCalledWith("/auth/verification-requested?token=token123"); + }); +}); diff --git a/apps/web/modules/auth/signup/components/signup-form.tsx b/apps/web/modules/auth/signup/components/signup-form.tsx index b2672dd5ea..08636c0f59 100644 --- a/apps/web/modules/auth/signup/components/signup-form.tsx +++ b/apps/web/modules/auth/signup/components/signup-form.tsx @@ -19,7 +19,6 @@ import { FormProvider, useForm } from "react-hook-form"; import toast from "react-hot-toast"; import Turnstile, { useTurnstile } from "react-turnstile"; import { z } from "zod"; -import { env } from "@formbricks/lib/env"; import { TOrganizationRole } from "@formbricks/types/memberships"; import { TUserLocale, ZUserName, ZUserPassword } from "@formbricks/types/user"; import { createEmailTokenAction } from "../../../auth/actions"; @@ -31,8 +30,6 @@ const ZSignupInput = z.object({ password: ZUserPassword, }); -const turnstileSiteKey = env.NEXT_PUBLIC_TURNSTILE_SITE_KEY; - type TSignupInput = z.infer; interface SignupFormProps { @@ -55,6 +52,7 @@ interface SignupFormProps { isTurnstileConfigured: boolean; samlTenant: string; samlProduct: string; + turnstileSiteKey?: string; } export const SignupForm = ({ @@ -77,6 +75,7 @@ export const SignupForm = ({ isTurnstileConfigured, samlTenant, samlProduct, + turnstileSiteKey, }: SignupFormProps) => { const [showLogin, setShowLogin] = useState(false); const searchParams = useSearchParams(); @@ -171,10 +170,11 @@ export const SignupForm = ({
field.onChange(name)} + onChange={(e) => field.onChange(e.target.value)} placeholder="Full name" className="bg-white" /> @@ -192,9 +192,10 @@ export const SignupForm = ({
field.onChange(email)} + onChange={(e) => field.onChange(e.target.value)} placeholder="work@email.com" className="bg-white" /> @@ -212,10 +213,11 @@ export const SignupForm = ({
field.onChange(password)} + onChange={(e) => field.onChange(e.target.value)} autoComplete="current-password" placeholder="*******" aria-placeholder="password" @@ -248,6 +250,7 @@ export const SignupForm = ({ {showLogin && (
diff --git a/apps/web/modules/setup/(fresh-instance)/signup/page.test.tsx b/apps/web/modules/setup/(fresh-instance)/signup/page.test.tsx new file mode 100644 index 0000000000..8d00159231 --- /dev/null +++ b/apps/web/modules/setup/(fresh-instance)/signup/page.test.tsx @@ -0,0 +1,97 @@ +import { getIsSamlSsoEnabled, getisSsoEnabled } from "@/modules/ee/license-check/lib/utils"; +import { getTranslate } from "@/tolgee/server"; +import "@testing-library/jest-dom/vitest"; +import { render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { findMatchingLocale } from "@formbricks/lib/utils/locale"; +import { SignupPage } from "./page"; + +// Mock dependencies + +vi.mock("@formbricks/lib/constants", () => ({ + IS_FORMBRICKS_CLOUD: false, + POSTHOG_API_KEY: "mock-posthog-api-key", + POSTHOG_HOST: "mock-posthog-host", + IS_POSTHOG_CONFIGURED: true, + ENCRYPTION_KEY: "mock-encryption-key", + ENTERPRISE_LICENSE_KEY: "mock-enterprise-license-key", + GITHUB_ID: "mock-github-id", + GITHUB_SECRET: "test-githubID", + GOOGLE_CLIENT_ID: "test-google-client-id", + GOOGLE_CLIENT_SECRET: "test-google-client-secret", + AZUREAD_CLIENT_ID: "test-azuread-client-id", + AZUREAD_CLIENT_SECRET: "test-azure", + AZUREAD_TENANT_ID: "test-azuread-tenant-id", + OIDC_DISPLAY_NAME: "test-oidc-display-name", + OIDC_CLIENT_ID: "test-oidc-client-id", + OIDC_ISSUER: "test-oidc-issuer", + OIDC_CLIENT_SECRET: "test-oidc-client-secret", + OIDC_SIGNING_ALGORITHM: "test-oidc-signing-algorithm", + WEBAPP_URL: "test-webapp-url", + IS_PRODUCTION: false, + SENTRY_DSN: "mock-sentry-dsn", + FB_LOGO_URL: "mock-fb-logo-url", + SMTP_HOST: "smtp.example.com", + SMTP_PORT: 587, + SMTP_USER: "smtp-user", + SAML_AUDIENCE: "test-saml-audience", + SAML_PATH: "test-saml-path", + SAML_DATABASE_URL: "test-saml-database-url", + TERMS_URL: "test-terms-url", + SIGNUP_ENABLED: true, + PRIVACY_URL: "test-privacy-url", + EMAIL_VERIFICATION_DISABLED: false, + EMAIL_AUTH_ENABLED: true, + GOOGLE_OAUTH_ENABLED: true, + GITHUB_OAUTH_ENABLED: true, + AZURE_OAUTH_ENABLED: true, + OIDC_OAUTH_ENABLED: true, + DEFAULT_ORGANIZATION_ID: "test-default-organization-id", + DEFAULT_ORGANIZATION_ROLE: "test-default-organization-role", + IS_TURNSTILE_CONFIGURED: true, + SAML_TENANT: "test-saml-tenant", + SAML_PRODUCT: "test-saml-product", + TURNSTILE_SITE_KEY: "test-turnstile-site-key", +})); + +vi.mock("@/modules/ee/license-check/lib/utils", () => ({ + getisSsoEnabled: vi.fn(), + getIsSamlSsoEnabled: vi.fn(), +})); + +vi.mock("@formbricks/lib/utils/locale", () => ({ + findMatchingLocale: vi.fn(), +})); + +vi.mock("@/tolgee/server", () => ({ + getTranslate: vi.fn(), +})); + +// Mock the SignupForm component to simplify our test assertions +vi.mock("@/modules/auth/signup/components/signup-form", () => ({ + SignupForm: (props) => ( +
+ SignupForm +
+ ), +})); + +describe("SignupPage", () => { + beforeEach(() => { + vi.mocked(getisSsoEnabled).mockResolvedValue(true); + vi.mocked(getIsSamlSsoEnabled).mockResolvedValue(false); + vi.mocked(findMatchingLocale).mockResolvedValue("en-US"); + vi.mocked(getTranslate).mockResolvedValue((key) => key); + }); + + it("renders the signup page correctly", async () => { + const page = await SignupPage(); + render(page); + + expect(screen.getByTestId("signup-form")).toBeInTheDocument(); + expect(screen.getByTestId("signup-form")).toHaveAttribute( + "data-turnstile-key", + "test-turnstile-site-key" + ); + }); +}); diff --git a/apps/web/modules/setup/(fresh-instance)/signup/page.tsx b/apps/web/modules/setup/(fresh-instance)/signup/page.tsx index 0f5c3bce31..3a15e432ee 100644 --- a/apps/web/modules/setup/(fresh-instance)/signup/page.tsx +++ b/apps/web/modules/setup/(fresh-instance)/signup/page.tsx @@ -18,6 +18,7 @@ import { SAML_PRODUCT, SAML_TENANT, TERMS_URL, + TURNSTILE_SITE_KEY, WEBAPP_URL, } from "@formbricks/lib/constants"; import { findMatchingLocale } from "@formbricks/lib/utils/locale"; @@ -59,6 +60,7 @@ export const SignupPage = async () => { isTurnstileConfigured={IS_TURNSTILE_CONFIGURED} samlTenant={SAML_TENANT} samlProduct={SAML_PRODUCT} + turnstileSiteKey={TURNSTILE_SITE_KEY} />
); diff --git a/apps/web/vite.config.mts b/apps/web/vite.config.mts index 6fa694f853..c889d2d59a 100644 --- a/apps/web/vite.config.mts +++ b/apps/web/vite.config.mts @@ -1,7 +1,7 @@ // vitest.config.ts -import react from "@vitejs/plugin-react"; import { PluginOption, loadEnv } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; +import react from "@vitejs/plugin-react"; import { defineConfig } from "vitest/config"; export default defineConfig({ @@ -19,14 +19,15 @@ export default defineConfig({ "modules/api/v2/**/*.ts", "modules/api/v2/**/*.tsx", "modules/auth/lib/**/*.ts", + "modules/signup/lib/**/*.ts", "modules/auth/signup/lib/**/*.ts", "modules/auth/signup/**/*.tsx", "modules/ee/whitelabel/email-customization/components/*.tsx", - "modules/ee/role-management/components/*.tsx", - "modules/organization/settings/teams/components/edit-memberships/organization-actions.tsx", "modules/email/components/email-template.tsx", "modules/email/emails/survey/follow-up.tsx", "modules/ui/components/post-hog-client/*.tsx", + "modules/ee/role-management/components/*.tsx", + "modules/organization/settings/teams/components/edit-memberships/organization-actions.tsx", "modules/ui/components/alert/*.tsx", "app/(app)/environments/**/layout.tsx", "app/(app)/environments/**/settings/(organization)/general/page.tsx", diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e6e5268159..cda136e076 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -98,7 +98,7 @@ x-environment: &environment ############################################# OPTIONAL (OAUTH CONFIGURATION) ############################################# # Set the below from Cloudflare Turnstile if you want to enable turnstile in signups - # NEXT_PUBLIC_TURNSTILE_SITE_KEY: + # TURNSTILE_SITE_KEY: # TURNSTILE_SECRET_KEY: # Set the below from GitHub if you want to enable GitHub OAuth diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index c2ca1ebb3c..aa51a417bf 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -261,7 +261,11 @@ export const BILLING_LIMITS = { } as const; export const AI_AZURE_LLM_RESSOURCE_NAME = env.AI_AZURE_LLM_RESSOURCE_NAME; - +export const AI_AZURE_LLM_API_KEY = env.AI_AZURE_LLM_API_KEY; +export const AI_AZURE_LLM_DEPLOYMENT_ID = env.AI_AZURE_LLM_DEPLOYMENT_ID; +export const AI_AZURE_EMBEDDINGS_RESSOURCE_NAME = env.AI_AZURE_EMBEDDINGS_RESSOURCE_NAME; +export const AI_AZURE_EMBEDDINGS_API_KEY = env.AI_AZURE_EMBEDDINGS_API_KEY; +export const AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID = env.AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID; export const IS_AI_CONFIGURED = Boolean( env.AI_AZURE_EMBEDDINGS_API_KEY && env.AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID && @@ -270,11 +274,6 @@ export const IS_AI_CONFIGURED = Boolean( env.AI_AZURE_LLM_DEPLOYMENT_ID && env.AI_AZURE_LLM_RESSOURCE_NAME ); -export const AI_AZURE_LLM_API_KEY = env.AI_AZURE_LLM_API_KEY; -export const AI_AZURE_LLM_DEPLOYMENT_ID = env.AI_AZURE_LLM_DEPLOYMENT_ID; -export const AI_AZURE_EMBEDDINGS_RESSOURCE_NAME = env.AI_AZURE_EMBEDDINGS_RESSOURCE_NAME; -export const AI_AZURE_EMBEDDINGS_API_KEY = env.AI_AZURE_EMBEDDINGS_API_KEY; -export const AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID = env.AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID; export const INTERCOM_SECRET_KEY = env.INTERCOM_SECRET_KEY; export const INTERCOM_APP_ID = env.INTERCOM_APP_ID; @@ -285,8 +284,8 @@ export const POSTHOG_API_HOST = env.POSTHOG_API_HOST; export const IS_POSTHOG_CONFIGURED = Boolean(POSTHOG_API_KEY && POSTHOG_API_HOST); export const TURNSTILE_SECRET_KEY = env.TURNSTILE_SECRET_KEY; - -export const IS_TURNSTILE_CONFIGURED = Boolean(env.NEXT_PUBLIC_TURNSTILE_SITE_KEY && TURNSTILE_SECRET_KEY); +export const TURNSTILE_SITE_KEY = env.TURNSTILE_SITE_KEY; +export const IS_TURNSTILE_CONFIGURED = Boolean(env.TURNSTILE_SITE_KEY && TURNSTILE_SECRET_KEY); export const IS_PRODUCTION = env.NODE_ENV === "production"; diff --git a/packages/lib/env.ts b/packages/lib/env.ts index ff68f505db..ec58a98e5f 100644 --- a/packages/lib/env.ts +++ b/packages/lib/env.ts @@ -102,6 +102,7 @@ export const env = createEnv({ .optional() .or(z.string().refine((str) => str === "")), TURNSTILE_SECRET_KEY: z.string().optional(), + TURNSTILE_SITE_KEY: z.string().optional(), UPLOADS_DIR: z.string().min(1).optional(), VERCEL_URL: z.string().optional(), WEBAPP_URL: z.string().url().optional(), @@ -128,7 +129,6 @@ export const env = createEnv({ .or(z.string().refine((str) => str === "")), NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: z.string().optional(), NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: z.string().optional(), - NEXT_PUBLIC_TURNSTILE_SITE_KEY: z.string().optional(), }, /* * Due to how Next.js bundles environment variables on Edge and Client, @@ -188,7 +188,6 @@ export const env = createEnv({ SENTRY_DSN: process.env.SENTRY_DSN, POSTHOG_API_KEY: process.env.POSTHOG_API_KEY, POSTHOG_API_HOST: process.env.POSTHOG_API_HOST, - NEXT_PUBLIC_TURNSTILE_SITE_KEY: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY, OPENTELEMETRY_LISTENER_URL: process.env.OPENTELEMETRY_LISTENER_URL, INTERCOM_APP_ID: process.env.INTERCOM_APP_ID, NOTION_OAUTH_CLIENT_ID: process.env.NOTION_OAUTH_CLIENT_ID, @@ -226,6 +225,7 @@ export const env = createEnv({ SURVEY_URL: process.env.SURVEY_URL, TELEMETRY_DISABLED: process.env.TELEMETRY_DISABLED, TURNSTILE_SECRET_KEY: process.env.TURNSTILE_SECRET_KEY, + TURNSTILE_SITE_KEY: process.env.TURNSTILE_SITE_KEY, TERMS_URL: process.env.TERMS_URL, UPLOADS_DIR: process.env.UPLOADS_DIR, VERCEL_URL: process.env.VERCEL_URL, diff --git a/turbo.json b/turbo.json index d86852a5d0..9be6433da0 100644 --- a/turbo.json +++ b/turbo.json @@ -162,7 +162,6 @@ "NEXT_PUBLIC_FORMBRICKS_COM_API_HOST", "NEXT_PUBLIC_FORMBRICKS_COM_ENVIRONMENT_ID", "NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID", - "NEXT_PUBLIC_TURNSTILE_SITE_KEY", "OPENTELEMETRY_LISTENER_URL", "NEXT_RUNTIME", "NEXTAUTH_SECRET", @@ -208,6 +207,7 @@ "SURVEY_URL", "TELEMETRY_DISABLED", "TURNSTILE_SECRET_KEY", + "TURNSTILE_SITE_KEY", "TERMS_URL", "UPLOADS_DIR", "VERCEL",