mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
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:
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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", () => ({
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
377
apps/web/modules/auth/signup/components/signup-form.test.tsx
Normal file
377
apps/web/modules/auth/signup/components/signup-form.test.tsx
Normal 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");
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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" } });
|
||||
|
||||
@@ -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>
|
||||
|
||||
97
apps/web/modules/setup/(fresh-instance)/signup/page.test.tsx
Normal file
97
apps/web/modules/setup/(fresh-instance)/signup/page.test.tsx
Normal 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"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user