chore: Refactored the Turnstile next public env variable and added test files (#4997)

Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
This commit is contained in:
victorvhs017
2025-04-07 03:07:39 -03:00
committed by GitHub
parent 9802536ded
commit 15878a4ac5
15 changed files with 567 additions and 46 deletions

View File

@@ -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

View File

@@ -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";

View File

@@ -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", () => ({

View File

@@ -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 () => {

View File

@@ -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) => (
<div
data-testid="turnstile"
onClick={() => {
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: () => <div data-testid="sso-options">SSOOptions</div>,
}));
vi.mock("@/modules/auth/signup/components/terms-privacy-links", () => ({
TermsPrivacyLinks: () => <div data-testid="terms-privacy-links">TermsPrivacyLinks</div>,
}));
vi.mock("@/modules/ui/components/button", () => ({
Button: (props: any) => <button {...props}>{props.children}</button>,
}));
vi.mock("@/modules/ui/components/input", () => ({
Input: (props: any) => <input {...props} />,
}));
vi.mock("@/modules/ui/components/password-input", () => ({
PasswordInput: (props: any) => <input type="password" {...props} />,
}));
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(<SignupForm {...defaultProps} />);
// 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(<SignupForm {...defaultProps} />);
// 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(<SignupForm {...props} />);
// 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(<SignupForm {...props} />);
// 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(<SignupForm {...props} />);
// 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(<SignupForm {...defaultProps} />);
// 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");
});
});

View File

@@ -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<typeof ZSignupInput>;
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 = ({
<FormControl>
<div>
<Input
data-testid="signup-name"
value={field.value}
name="name"
autoFocus
onChange={(name) => field.onChange(name)}
onChange={(e) => field.onChange(e.target.value)}
placeholder="Full name"
className="bg-white"
/>
@@ -192,9 +192,10 @@ export const SignupForm = ({
<FormControl>
<div>
<Input
data-testid="signup-email"
value={field.value}
name="email"
onChange={(email) => field.onChange(email)}
onChange={(e) => field.onChange(e.target.value)}
placeholder="work@email.com"
className="bg-white"
/>
@@ -212,10 +213,11 @@ export const SignupForm = ({
<FormControl>
<div>
<PasswordInput
data-testid="signup-password"
id="password"
name="password"
value={field.value}
onChange={(password) => 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 && (
<Button
data-testid="signup-submit"
type="submit"
className="h-10 w-full justify-center"
loading={form.formState.isSubmitting}
@@ -258,6 +261,7 @@ export const SignupForm = ({
{!showLogin && (
<Button
data-testid="signup-show-login"
type="button"
onClick={() => {
setShowLogin(true);

View File

@@ -4,6 +4,7 @@ import {
getIsSamlSsoEnabled,
getisSsoEnabled,
} from "@/modules/ee/license-check/lib/utils";
import "@testing-library/jest-dom/vitest";
import { cleanup, render, screen } from "@testing-library/react";
import { notFound } from "next/navigation";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -50,23 +51,50 @@ vi.mock("next/navigation", () => ({
// Mock environment variables and constants
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,
EMAIL_AUTH_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,
OIDC_DISPLAY_NAME: "OpenID",
SAML_OAUTH_ENABLED: true,
SAML_TENANT: "test-tenant",
SAML_PRODUCT: "test-product",
DEFAULT_ORGANIZATION_ID: "test-default-organization-id",
DEFAULT_ORGANIZATION_ROLE: "test-default-organization-role",
IS_TURNSTILE_CONFIGURED: true,
WEBAPP_URL: "http://localhost:3000",
TERMS_URL: "http://localhost:3000/terms",
PRIVACY_URL: "http://localhost:3000/privacy",
DEFAULT_ORGANIZATION_ID: "test-org-id",
DEFAULT_ORGANIZATION_ROLE: "admin",
SAML_TENANT: "test-saml-tenant",
SAML_PRODUCT: "test-saml-product",
TURNSTILE_SITE_KEY: "test-turnstile-site-key",
SAML_OAUTH_ENABLED: true,
}));
describe("SignupPage", () => {
@@ -88,8 +116,11 @@ describe("SignupPage", () => {
vi.mocked(getIsMultiOrgEnabled).mockResolvedValue(true);
vi.mocked(getisSsoEnabled).mockResolvedValue(true);
vi.mocked(getIsSamlSsoEnabled).mockResolvedValue(true);
vi.mocked(findMatchingLocale).mockResolvedValue("en");
vi.mocked(verifyInviteToken).mockReturnValue({ inviteId: "test-invite-id" });
vi.mocked(findMatchingLocale).mockResolvedValue("en-US");
vi.mocked(verifyInviteToken).mockReturnValue({
inviteId: "test-invite-id",
email: "test@example.com",
});
vi.mocked(getIsValidInviteToken).mockResolvedValue(true);
const result = await SignupPage({ searchParams: mockSearchParams });
@@ -128,7 +159,10 @@ describe("SignupPage", () => {
it("calls notFound when invite token is valid but invite is not found", async () => {
// Mock the license check functions to return false
vi.mocked(getIsMultiOrgEnabled).mockResolvedValue(false);
vi.mocked(verifyInviteToken).mockReturnValue({ inviteId: "test-invite-id" });
vi.mocked(verifyInviteToken).mockReturnValue({
inviteId: "test-invite-id",
email: "test@example.com",
});
vi.mocked(getIsValidInviteToken).mockResolvedValue(false);
await SignupPage({ searchParams: { inviteToken: "test-token" } });
@@ -141,8 +175,11 @@ describe("SignupPage", () => {
vi.mocked(getIsMultiOrgEnabled).mockResolvedValue(true);
vi.mocked(getisSsoEnabled).mockResolvedValue(true);
vi.mocked(getIsSamlSsoEnabled).mockResolvedValue(true);
vi.mocked(findMatchingLocale).mockResolvedValue("en");
vi.mocked(verifyInviteToken).mockReturnValue({ inviteId: "test-invite-id" });
vi.mocked(findMatchingLocale).mockResolvedValue("en-US");
vi.mocked(verifyInviteToken).mockReturnValue({
inviteId: "test-invite-id",
email: "test@example.com",
});
vi.mocked(getIsValidInviteToken).mockResolvedValue(true);
const result = await SignupPage({ searchParams: { email: "test@example.com" } });

View File

@@ -24,6 +24,7 @@ import {
SAML_TENANT,
SIGNUP_ENABLED,
TERMS_URL,
TURNSTILE_SITE_KEY,
WEBAPP_URL,
} from "@formbricks/lib/constants";
import { verifyInviteToken } from "@formbricks/lib/jwt";
@@ -83,6 +84,7 @@ export const SignupPage = async ({ searchParams: searchParamsProps }) => {
isTurnstileConfigured={IS_TURNSTILE_CONFIGURED}
samlTenant={SAML_TENANT}
samlProduct={SAML_PRODUCT}
turnstileSiteKey={TURNSTILE_SITE_KEY}
/>
</FormWrapper>
</div>

View File

@@ -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) => (
<div data-testid="signup-form" data-turnstile-key={props.turnstileSiteKey}>
SignupForm
</div>
),
}));
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"
);
});
});

View File

@@ -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}
/>
</div>
);

View File

@@ -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",

View File

@@ -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

View File

@@ -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";

View File

@@ -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,

View File

@@ -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",