chore: update version for 1.6 release (#2123)

This commit is contained in:
Matti Nannt
2024-02-26 22:31:52 +01:00
committed by GitHub
parent 60b474a4f9
commit 47ec19b46d
73 changed files with 2974 additions and 3920 deletions

View File

@@ -30,8 +30,5 @@ jobs:
SECRET=$(openssl rand -hex 32)
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
- name: Build formbricks-js dependencies
run: pnpm build --filter=js
- name: Test
run: pnpm test

View File

@@ -103,7 +103,7 @@ Formbricks is both a free and open source survey platform - and a privacy-first
- 🔒 [Auth.js](https://authjs.dev/)
- 🧘‍♂️ [Zod](https://zod.dev/)
-
- 🐛 [Vitest](https://vitest.dev/)
<a id="getting-started"></a>

View File

@@ -1,4 +1,5 @@
import Image from "next/image";
import CorsHandling from "./cors-handling-in-api.webp";
export const metadata = {
@@ -25,7 +26,7 @@ Thank you for choosing to contribute to Formbricks. Before you start, please fam
- Constants should be in the packages folder
- Types should be in the packages folder
- How we handle Pull Requests
- Read environment variables from `.env.mjs`
- Read server-side environment variables from `constants.ts`
---
@@ -83,9 +84,9 @@ You should store constants in `packages/lib/constants`
You should store type in `packages/types`
## Read environment variables from `.env.mjs`
## Read server-side environment variables from `constants.ts`
Environment variables (`process.env`) shouldnt be accessed directly but be added in the `.env.mjs` and should be accessed from here. This practice helps us ensure that the variables are typesafe.
Server-side environment variables (`process.env`) shouldnt be accessed directly but included into the `constants.ts` file and read from there. This way we can assure they are used only on the server side and are also type-safe.
## How we handle Pull Requests

View File

@@ -46,7 +46,7 @@ All you need to do is copy a `<script>` tag to your HTML head, and thats abou
```html {{ title: 'index.html' }}
<!-- START Formbricks Surveys -->
<script type="text/javascript">
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^1.5.1/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "<your-environment-id>", apiHost: "<api-host>"})},500)}();
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^1.6.0/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "<your-environment-id>", apiHost: "<api-host>"})},500)}();
</script>
<!-- END Formbricks Surveys -->
```
@@ -397,20 +397,24 @@ Enabling Formbricks debug mode in your browser is a useful troubleshooting step
To activate Formbricks debug mode:
1. **In Your Integration Code:**
- Locate the initialization code for Formbricks in your application (HTML, ReactJS, NextJS, VueJS).
- Set the `debug` option to `true` when initializing Formbricks.
2. **View Debug Logs:**
- Open your browser's developer tools by pressing `F12` or right-clicking and selecting "Inspect."
- Navigate to the "Console" tab to view Formbricks debugging information.
**How to Open Browser Console:**
- **Google Chrome:** Press `F12` or right-click, select "Inspect," and go to the "Console" tab.
- **Firefox:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
- **Safari:** Press `Option + Command + C` to open the developer tools and navigate to the "Console" tab.
- **Edge:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
3. **Via URL Parameter:**
- For quick activation, add `?formbricksDebug=true` to your application's URL.
This parameter will enable debugging for the current session.
@@ -418,6 +422,7 @@ To activate Formbricks debug mode:
### Common Use Cases
Debug mode is beneficial for scenarios such as:
- Verifying Formbricks functionality.
- Identifying integration issues.
- Troubleshooting unexpected behavior.
@@ -425,6 +430,7 @@ Debug mode is beneficial for scenarios such as:
### Debug Log Messages
Specific debug log messages may provide insights into:
- API calls and responses.
- Event tracking and form interactions.
- Integration errors.

View File

@@ -4,7 +4,7 @@ import { formbricksEnabled } from "@/app/lib/formbricks";
import { useEffect } from "react";
import formbricks from "@formbricks/js";
import { env } from "@formbricks/lib/env.mjs";
import { env } from "@formbricks/lib/env";
type UsageAttributesUpdaterProps = {
numSurveys: number;

View File

@@ -4,7 +4,7 @@ import type { Session } from "next-auth";
import { usePostHog } from "posthog-js/react";
import { useEffect } from "react";
import { env } from "@formbricks/lib/env.mjs";
import { env } from "@formbricks/lib/env";
const posthogEnabled = env.NEXT_PUBLIC_POSTHOG_API_KEY && env.NEXT_PUBLIC_POSTHOG_API_HOST;

View File

@@ -6,7 +6,7 @@ import { useEffect, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
import { env } from "@formbricks/lib/env.mjs";
import { env } from "@formbricks/lib/env";
import { TUser, TUserObjective } from "@formbricks/types/user";
import { Button } from "@formbricks/ui/Button";

View File

@@ -7,7 +7,7 @@ import { useEffect, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
import { env } from "@formbricks/lib/env.mjs";
import { env } from "@formbricks/lib/env";
import { Button } from "@formbricks/ui/Button";
import { handleTabNavigation } from "../utils";

View File

@@ -5,9 +5,8 @@ import { responses } from "@/app/lib/api/response";
import { headers } from "next/headers";
import { NextRequest } from "next/server";
import { UPLOADS_DIR } from "@formbricks/lib/constants";
import { ENCRYPTION_KEY, UPLOADS_DIR } from "@formbricks/lib/constants";
import { validateLocalSignedUrl } from "@formbricks/lib/crypto";
import { env } from "@formbricks/lib/env.mjs";
import { putFileToLocalStorage } from "@formbricks/lib/storage/service";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
@@ -91,7 +90,7 @@ export async function POST(req: NextRequest, context: Context): Promise<Response
fileType,
Number(signedTimestamp),
signedSignature,
env.ENCRYPTION_KEY
ENCRYPTION_KEY
);
if (!validated) {

View File

@@ -7,9 +7,8 @@ import { headers } from "next/headers";
import { NextRequest } from "next/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { UPLOADS_DIR } from "@formbricks/lib/constants";
import { ENCRYPTION_KEY, UPLOADS_DIR } from "@formbricks/lib/constants";
import { validateLocalSignedUrl } from "@formbricks/lib/crypto";
import { env } from "@formbricks/lib/env.mjs";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { putFileToLocalStorage } from "@formbricks/lib/storage/service";
@@ -72,7 +71,7 @@ export async function POST(req: NextRequest): Promise<Response> {
fileType,
Number(signedTimestamp),
signedSignature,
env.ENCRYPTION_KEY
ENCRYPTION_KEY
);
if (!validated) {

View File

@@ -1,12 +1,13 @@
import { prisma } from "@formbricks/database";
import {
DEFAULT_TEAM_ID,
DEFAULT_TEAM_ROLE,
EMAIL_AUTH_ENABLED,
EMAIL_VERIFICATION_DISABLED,
INVITE_DISABLED,
SIGNUP_ENABLED,
} from "@formbricks/lib/constants";
import { sendInviteAcceptedEmail, sendVerificationEmail } from "@formbricks/lib/emails/emails";
import { env } from "@formbricks/lib/env.mjs";
import { deleteInvite } from "@formbricks/lib/invite/service";
import { verifyInviteToken } from "@formbricks/lib/jwt";
import { createMembership } from "@formbricks/lib/membership/service";
@@ -63,16 +64,16 @@ export async function POST(request: Request) {
// User signs up without invite
// Default team assignment is enabled
if (env.DEFAULT_TEAM_ID && env.DEFAULT_TEAM_ID.length > 0) {
if (DEFAULT_TEAM_ID && DEFAULT_TEAM_ID.length > 0) {
// check if team exists
let team = await getTeam(env.DEFAULT_TEAM_ID);
let team = await getTeam(DEFAULT_TEAM_ID);
let isNewTeam = false;
if (!team) {
// create team with id from env
team = await createTeam({ id: env.DEFAULT_TEAM_ID, name: user.name + "'s Team" });
team = await createTeam({ id: DEFAULT_TEAM_ID, name: user.name + "'s Team" });
isNewTeam = true;
}
const role = isNewTeam ? "owner" : env.DEFAULT_TEAM_ROLE || "admin";
const role = isNewTeam ? "owner" : DEFAULT_TEAM_ROLE || "admin";
await createMembership(team.id, user.id, { role, accepted: true });
}
// Without default team assignment

View File

@@ -1,5 +1,5 @@
import formbricks from "@formbricks/js";
import { env } from "@formbricks/lib/env.mjs";
import { env } from "@formbricks/lib/env";
export const formbricksEnabled =
typeof env.NEXT_PUBLIC_FORMBRICKS_API_HOST && env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID;

View File

@@ -1,7 +1,7 @@
import cuid2 from "@paralleldrive/cuid2";
import { ENCRYPTION_KEY, FORMBRICKS_ENCRYPTION_KEY } from "@formbricks/lib/constants";
import { decryptAES128, symmetricDecrypt, symmetricEncrypt } from "@formbricks/lib/crypto";
import { env } from "@formbricks/lib/env.mjs";
// generate encrypted single use id for the survey
export const generateSurveySingleUseId = (isEncrypted: boolean): string => {
@@ -10,7 +10,7 @@ export const generateSurveySingleUseId = (isEncrypted: boolean): string => {
return cuid;
}
const encryptedCuid = symmetricEncrypt(cuid, env.ENCRYPTION_KEY);
const encryptedCuid = symmetricEncrypt(cuid, ENCRYPTION_KEY);
return encryptedCuid;
};
@@ -20,13 +20,13 @@ export const validateSurveySingleUseId = (surveySingleUseId: string): string | u
let decryptedCuid: string | null = null;
if (surveySingleUseId.length === 64) {
if (!env.FORMBRICKS_ENCRYPTION_KEY) {
if (!FORMBRICKS_ENCRYPTION_KEY) {
throw new Error("FORMBRICKS_ENCRYPTION_KEY is not defined");
}
decryptedCuid = decryptAES128(env.FORMBRICKS_ENCRYPTION_KEY!, surveySingleUseId);
decryptedCuid = decryptAES128(FORMBRICKS_ENCRYPTION_KEY!, surveySingleUseId);
} else {
decryptedCuid = symmetricDecrypt(surveySingleUseId, env.ENCRYPTION_KEY);
decryptedCuid = symmetricDecrypt(surveySingleUseId, ENCRYPTION_KEY);
}
if (cuid2.isCuid(decryptedCuid)) {

View File

@@ -1,7 +1,10 @@
import { createId } from "@paralleldrive/cuid2";
import { withSentryConfig } from "@sentry/nextjs";
import createJiti from "jiti";
import "@formbricks/lib/env.mjs";
const jiti = createJiti(new URL(import.meta.url).pathname);
jiti("@formbricks/lib/env");
/** @type {import('next').NextConfig} */

View File

@@ -1,6 +1,6 @@
{
"name": "@formbricks/web",
"version": "1.5.1",
"version": "1.6.0",
"private": true,
"scripts": {
"clean": "rimraf .turbo node_modules .next",
@@ -26,7 +26,6 @@
"@json2csv/node": "^7.0.6",
"@paralleldrive/cuid2": "^2.2.2",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@react-email/components": "^0.0.15",
"@sentry/nextjs": "^7.102.1",
"@vercel/og": "^0.6.2",
@@ -34,17 +33,18 @@
"bcryptjs": "^2.4.3",
"dotenv": "^16.4.5",
"encoding": "^0.1.13",
"framer-motion": "11.0.5",
"framer-motion": "11.0.6",
"googleapis": "^133.0.0",
"jiti": "^1.21.0",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"lru-cache": "^10.2.0",
"lucide-react": "^0.336.0",
"lucide-react": "^0.339.0",
"mime": "^4.0.1",
"next": "14.1.0",
"nodemailer": "^6.9.10",
"otplib": "^12.0.1",
"posthog-js": "^1.108.2",
"posthog-js": "^1.108.3",
"prismjs": "^1.29.0",
"qrcode": "^1.5.3",
"react": "18.2.0",

View File

@@ -1,7 +1,7 @@
{
"name": "@formbricks/api",
"license": "MIT",
"version": "1.5.0",
"version": "1.6.0",
"description": "Formbricks-api is an api wrapper for the Formbricks client API",
"keywords": [
"Formbricks",
@@ -32,12 +32,15 @@
"clean": "rimraf .turbo node_modules dist"
},
"devDependencies": {
"@formbricks/types": "workspace:*",
"@formbricks/tsconfig": "workspace:*",
"@formbricks/types": "workspace:*",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "1.10.12",
"terser": "^5.27.0",
"vite": "^5.0.12",
"vite-plugin-dts": "^3.7.2"
"terser": "^5.28.1",
"vite": "^5.1.4",
"vite-plugin-dts": "^3.7.3"
},
"dependencies": {
"@t3-oss/env-nextjs": "^0.9.2"
}
}

View File

@@ -23,20 +23,22 @@
"lint": "eslint ./src --fix",
"post-install": "pnpm generate",
"predev": "pnpm generate",
"data-migration:1.6": "ts-node ./migrations/20240207041922_advanced_targeting/data-migration.ts"
"data-migration:v1.6": "ts-node ./migrations/20240207041922_advanced_targeting/data-migration.ts"
},
"dependencies": {
"@prisma/client": "^5.8.1",
"@prisma/extension-accelerate": "^0.6.2",
"@prisma/client": "^5.10.2",
"@prisma/extension-accelerate": "^0.6.3",
"@t3-oss/env-nextjs": "^0.9.2",
"dotenv-cli": "^7.3.0"
},
"devDependencies": {
"@formbricks/tsconfig": "workspace:*",
"@formbricks/types": "workspace:*",
"@paralleldrive/cuid2": "^2.2.2",
"eslint-config-formbricks": "workspace:*",
"prisma": "^5.8.1",
"prisma-dbml-generator": "^0.10.0",
"prisma-json-types-generator": "^3.0.3",
"prisma": "^5.10.2",
"prisma-dbml-generator": "^0.12.0",
"prisma-json-types-generator": "^3.0.4",
"ts-node": "^10.9.2",
"zod": "^3.22.4",
"zod-prisma": "^0.5.4"

View File

@@ -1,15 +0,0 @@
import { PrismaClient } from "@prisma/client";
import { DeepMockProxy, mockDeep, mockReset } from "jest-mock-extended";
import { prisma } from "./client";
jest.mock("./client", () => ({
__esModule: true,
prisma: mockDeep<PrismaClient>(),
}));
export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>;
beforeEach(() => {
mockReset(prismaMock);
});

View File

@@ -1,14 +1,16 @@
import Stripe from "stripe";
import { env } from "@formbricks/lib/env";
import { handleCheckoutSessionCompleted } from "../handlers/checkoutSessionCompleted";
import { handleSubscriptionUpdatedOrCreated } from "../handlers/subscriptionCreatedOrUpdated";
import { handleSubscriptionDeleted } from "../handlers/subscriptionDeleted";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
apiVersion: "2023-10-16",
});
const webhookSecret: string = process.env.STRIPE_WEBHOOK_SECRET!;
const webhookSecret: string = env.STRIPE_WEBHOOK_SECRET!;
const webhookHandler = async (requestBody: string, stripeSignature: string) => {
let event: Stripe.Event;

View File

@@ -1,5 +1,6 @@
import Stripe from "stripe";
import { env } from "@formbricks/lib/env";
import {
getMonthlyActiveTeamPeopleCount,
getMonthlyTeamResponseCount,
@@ -10,7 +11,7 @@ import {
import { ProductFeatureKeys, StripePriceLookupKeys, StripeProductNames } from "../lib/constants";
import { reportUsage } from "../lib/reportUsage";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: "2023-10-16",
});

View File

@@ -1,5 +1,6 @@
import Stripe from "stripe";
import { env } from "@formbricks/lib/env";
import {
getMonthlyActiveTeamPeopleCount,
getMonthlyTeamResponseCount,
@@ -10,7 +11,7 @@ import {
import { ProductFeatureKeys, StripePriceLookupKeys, StripeProductNames } from "../lib/constants";
import { reportUsage } from "../lib/reportUsage";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: "2023-10-16",
});

View File

@@ -1,11 +1,12 @@
import Stripe from "stripe";
import { env } from "@formbricks/lib/env";
import { getTeam, updateTeam } from "@formbricks/lib/team/service";
import { ProductFeatureKeys, StripeProductNames } from "../lib/constants";
import { unsubscribeCoreAndAppSurveyFeatures, unsubscribeLinkSurveyProFeatures } from "../lib/downgradePlan";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: "2023-10-16",
});

View File

@@ -1,6 +1,8 @@
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
import { env } from "@formbricks/lib/env";
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: "2023-10-16",
});

View File

@@ -1,11 +1,12 @@
import Stripe from "stripe";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { env } from "@formbricks/lib/env";
import { getTeam } from "@formbricks/lib/team/service";
import { StripePriceLookupKeys } from "./constants";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
apiVersion: "2023-10-16",
});

View File

@@ -1,12 +1,13 @@
import Stripe from "stripe";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { env } from "@formbricks/lib/env";
import { getTeam, updateTeam } from "@formbricks/lib/team/service";
import { StripePriceLookupKeys } from "./constants";
import { getFirstOfNextMonthTimestamp } from "./createSubscription";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
apiVersion: "2023-10-16",
});

View File

@@ -1,8 +1,10 @@
import Stripe from "stripe";
import { env } from "@formbricks/lib/env";
import { ProductFeatureKeys } from "./constants";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: "2023-10-16",
});

View File

@@ -18,6 +18,7 @@
},
"dependencies": {
"@formbricks/lib": "workspace:*",
"stripe": "^14.13.0"
"@t3-oss/env-nextjs": "^0.9.2",
"stripe": "^14.18.0"
}
}

View File

@@ -8,13 +8,16 @@
"clean": "rimraf node_modules .turbo"
},
"devDependencies": {
"eslint": "^8.56.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "1.10.12",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-storybook": "^0.6.15"
"eslint-plugin-storybook": "^0.8.0"
},
"dependencies": {
"@t3-oss/env-nextjs": "^0.9.2"
}
}

View File

@@ -1,6 +0,0 @@
module.exports = (api) => {
return {
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"],
plugins: [["@babel/plugin-transform-react-jsx", { runtime: api.env("test") ? "automatic" : "classic" }]],
};
};

View File

@@ -1,7 +1,7 @@
{
"name": "@formbricks/js",
"license": "MIT",
"version": "1.5.1",
"version": "1.6.0",
"description": "Formbricks-js allows you to connect your app to Formbricks, display surveys and trigger events.",
"homepage": "https://formbricks.com",
"repository": {
@@ -35,44 +35,29 @@
"build:dev": "tsc && vite build --mode dev",
"go": "vite build --watch --mode dev",
"lint": "eslint ./src --fix",
"test": "jest --coverage --no-cache",
"clean": "rimraf .turbo node_modules dist coverage"
},
"author": "Formbricks <hola@formbricks.com>",
"devDependencies": {
"@babel/core": "^7.23.7",
"@babel/preset-env": "^7.23.8",
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-typescript": "^7.23.3",
"@formbricks/api": "workspace:*",
"@formbricks/lib": "workspace:*",
"@formbricks/surveys": "workspace:*",
"@formbricks/tsconfig": "workspace:*",
"@formbricks/types": "workspace:*",
"@types/jest": "^29.5.11",
"@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.1",
"babel-jest": "^29.7.0",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"cross-env": "^7.0.3",
"isomorphic-fetch": "^3.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-fetch-mock": "^3.0.3",
"terser": "^5.27.0",
"vite": "^5.0.12",
"vite-plugin-dts": "^3.7.2",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "1.10.12"
"eslint-config-turbo": "1.10.12",
"isomorphic-fetch": "^3.0.0",
"terser": "^5.28.1",
"vite": "^5.1.4",
"vite-plugin-dts": "^3.7.3"
},
"jest": {
"transformIgnorePatterns": [
"!node_modules/"
],
"setupFiles": [
"<rootDir>/tests/__mocks__/setupTests.js"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/tests/__mocks__/fileMock.js",
"\\.(css|less)$": "<rootDir>/tests/__mocks__/styleMock.js"
}
"dependencies": {
"@t3-oss/env-nextjs": "^0.9.2"
}
}

View File

@@ -1,204 +0,0 @@
import fetchMock from "jest-fetch-mock";
import { constants } from "../constants";
const {
environmentId,
sessionId,
expiryTime,
surveyId,
questionOneId,
questionTwoId,
choiceOneId,
choiceTwoId,
choiceThreeId,
initialPersonUid,
initialUserId,
initialUserEmail,
newPersonUid,
eventIdForRouteChange,
updatedUserEmail,
customAttributeKey,
customAttributeValue,
eventIdForEventTracking,
} = constants;
export const mockInitResponse = () => {
fetchMock.mockResponseOnce(
JSON.stringify({
data: {
person: {
id: initialPersonUid,
createdAt: "2021-03-09T15:00:00.000Z",
updatedAt: "2021-03-09T15:00:00.000Z",
attributes: {},
},
surveys: [
{
id: surveyId,
questions: [
{
id: questionOneId,
type: "multipleChoiceSingle",
choices: [
{
id: choiceOneId,
label: "Not at all disappointed",
},
{
id: choiceTwoId,
label: "Somewhat disappointed",
},
{
id: choiceThreeId,
label: "Very disappointed",
},
],
headline: "How disappointed would you be if you could no longer use Test-Formbricks?",
required: true,
subheader: "Please select one of the following options:",
},
{
id: questionTwoId,
type: "openText",
headline: "How can we improve Test-Formbricks for you?",
required: true,
subheader: "Please be as specific as possible.",
},
],
triggers: [],
thankYouCard: {
enabled: true,
headline: "Thank you!",
subheader: "We appreciate your feedback.",
},
autoClose: null,
delay: 0,
},
],
noCodeActionClasses: [],
product: {
noCodeEvents: [],
brandColor: "#20b398",
linkSurveyBranding: true,
inAppBranding: true,
placement: "bottomRight",
darkOverlay: false,
clickOutsideClose: true,
},
},
})
);
};
export const mockSetUserIdResponse = () => {
fetchMock.mockResponseOnce(
JSON.stringify({
data: {
surveys: [],
session: {
id: sessionId,
createdAt: "2021-03-09T15:00:00.000Z",
updatedAt: "2021-03-09T15:00:00.000Z",
expiresAt: expiryTime,
},
noCodeActionClasses: [],
person: {
id: initialPersonUid,
environmentId,
attributes: { userId: initialUserId },
},
},
})
);
};
export const mockSetEmailIdResponse = () => {
fetchMock.mockResponseOnce(
JSON.stringify({
data: {
id: initialPersonUid,
environmentId,
attributes: { userId: initialUserId, email: initialUserEmail },
},
})
);
};
export const mockSetCustomAttributeResponse = () => {
fetchMock.mockResponseOnce(
JSON.stringify({
data: {
id: initialPersonUid,
environmentId,
attributes: {
userId: initialUserId,
email: initialUserEmail,
[customAttributeKey]: customAttributeValue,
},
},
})
);
};
export const mockUpdateEmailResponse = () => {
fetchMock.mockResponseOnce(
JSON.stringify({
data: {
id: initialPersonUid,
environmentId,
attributes: {
userId: initialUserId,
email: updatedUserEmail,
[customAttributeKey]: customAttributeValue,
},
},
})
);
};
export const mockEventTrackResponse = () => {
fetchMock.mockResponseOnce(
JSON.stringify({
id: eventIdForEventTracking,
})
);
console.log('Formbricks: Event "Button Clicked" tracked');
};
export const mockRefreshResponse = () => {
fetchMock.mockResponseOnce(JSON.stringify({}));
console.log("Settings refreshed");
};
export const mockRegisterRouteChangeResponse = () => {
fetchMock.mockResponseOnce(
JSON.stringify({
id: eventIdForRouteChange,
})
);
console.log("Checking page url: http://localhost/");
};
export const mockResetResponse = () => {
fetchMock.mockResponseOnce(
JSON.stringify({
data: {
surveys: [],
person: {
id: newPersonUid,
environmentId,
attributes: [],
},
session: {
id: sessionId,
createdAt: "2021-03-09T15:00:00.000Z",
updatedAt: "2021-03-09T15:00:00.000Z",
expiresAt: expiryTime,
},
noCodeActionClasses: [],
},
})
);
console.log("Resetting person. Getting new person, session and settings from backend");
};

View File

@@ -1 +0,0 @@
module.exports = 'placeholer-to-not-mock-all-files-as-js';

View File

@@ -1,10 +0,0 @@
/** @type {import('jest').Config} */
const config = {
verbose: true,
testEnvironment: "jsdom"
};
import fetchMock from "jest-fetch-mock";
fetchMock.enableMocks();
module.exports = config;

View File

@@ -1 +0,0 @@
module.exports = {};

View File

@@ -1,59 +0,0 @@
const generateUserId = () => {
const min = 1000;
const max = 9999;
const randomNum = Math.floor(Math.random() * (max - min + 1) + min);
return randomNum.toString();
};
const generateEmailId = () => {
const domain = "formbricks.test";
const randomString = Math.random().toString(36).substring(2);
const emailId = `${randomString}@${domain}`;
return emailId;
};
const generateRandomString = () => {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const maxLength = 8;
let randomString = "";
for (let i = 0; i < maxLength; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
randomString += characters.charAt(randomIndex);
}
return randomString;
};
const getOneDayExpiryTime = () => {
var ms = new Date().getTime();
var oneDayMs = 24 * 60 * 60 * 1000; // Number of milliseconds in one day
var expiryOfOneDay = ms + oneDayMs;
return expiryOfOneDay;
};
export const constants = {
environmentId: "mockedEnvironmentId",
apiHost: "mockedApiHost",
sessionId: generateRandomString(),
expiryTime: getOneDayExpiryTime(),
surveyId: generateRandomString(),
questionOneId: generateRandomString(),
questionTwoId: generateRandomString(),
choiceOneId: generateRandomString(),
choiceTwoId: generateRandomString(),
choiceThreeId: generateRandomString(),
choiceFourId: generateRandomString(),
initialPersonUid: generateRandomString(),
newPersonUid: generateRandomString(),
initialUserId: generateUserId(),
initialUserEmail: generateEmailId(),
updatedUserEmail: generateEmailId(),
customAttributeKey: generateRandomString(),
customAttributeValue: generateRandomString(),
userIdAttributeId: generateRandomString(),
userInitialEmailAttributeId: generateRandomString(),
userCustomAttrAttributeId: generateRandomString(),
userUpdatedEmailAttributeId: generateRandomString(),
eventIdForEventTracking: generateRandomString(),
eventIdForRouteChange: generateRandomString(),
} as const;

View File

@@ -1,140 +0,0 @@
/**
* @jest-environment jsdom
*/
import {
mockEventTrackResponse,
mockInitResponse,
mockRegisterRouteChangeResponse,
mockResetResponse,
mockSetCustomAttributeResponse,
mockSetEmailIdResponse,
mockSetUserIdResponse,
mockUpdateEmailResponse,
} from "./__mocks__/apiMock";
import { TPersonAttributes } from "@formbricks/types/people";
import formbricks from "../src/index";
import { constants } from "./constants";
const consoleLogMock = jest.spyOn(console, "log").mockImplementation();
test("Test Jest", () => {
expect(1 + 9).toBe(10);
});
const {
environmentId,
apiHost,
initialUserId,
initialUserEmail,
updatedUserEmail,
customAttributeKey,
customAttributeValue,
} = constants;
beforeEach(() => {
fetchMock.resetMocks();
});
/*
test("Formbricks should Initialise", async () => {
mockInitResponse();
await formbricks.init({
environmentId,
apiHost,
userId: initialUserId,
});
const configFromBrowser = localStorage.getItem("formbricks-js");
expect(configFromBrowser).toBeTruthy();
if (configFromBrowser) {
const jsonSavedConfig = JSON.parse(configFromBrowser);
expect(jsonSavedConfig.environmentId).toStrictEqual(environmentId);
expect(jsonSavedConfig.apiHost).toStrictEqual(apiHost);
}
});
test("Formbricks should set email", async () => {
mockSetEmailIdResponse();
await formbricks.setEmail(initialUserEmail);
const currentStatePerson = formbricks.getPerson();
const currentStatePersonAttributes = currentStatePerson.attributes;
const numberOfUserAttributes = Object.keys(currentStatePersonAttributes).length;
expect(numberOfUserAttributes).toStrictEqual(2);
const userId = currentStatePersonAttributes.userId;
expect(userId).toStrictEqual(initialUserId);
const email = currentStatePersonAttributes.email;
expect(email).toStrictEqual(initialUserEmail);
});
test("Formbricks should set custom attribute", async () => {
mockSetCustomAttributeResponse();
await formbricks.setAttribute(customAttributeKey, customAttributeValue);
const currentStatePerson = formbricks.getPerson();
const currentStatePersonAttributes = currentStatePerson.attributes;
const numberOfUserAttributes = Object.keys(currentStatePersonAttributes).length;
expect(numberOfUserAttributes).toStrictEqual(3);
const userId = currentStatePersonAttributes.userId;
expect(userId).toStrictEqual(initialUserId);
const email = currentStatePersonAttributes.email;
expect(email).toStrictEqual(initialUserEmail);
const customAttribute = currentStatePersonAttributes[customAttributeKey];
expect(customAttribute).toStrictEqual(customAttributeValue);
});
test("Formbricks should update attribute", async () => {
mockUpdateEmailResponse();
await formbricks.setEmail(updatedUserEmail);
const currentStatePerson = formbricks.getPerson();
const currentStatePersonAttributes = currentStatePerson.attributes;
const numberOfUserAttributes = Object.keys(currentStatePersonAttributes).length;
expect(numberOfUserAttributes).toStrictEqual(3);
const userId = currentStatePersonAttributes.userId;
expect(userId).toStrictEqual(initialUserId);
const email = currentStatePersonAttributes.email;
expect(email).toStrictEqual(updatedUserEmail);
const customAttribute = currentStatePersonAttributes[customAttributeKey];
expect(customAttribute).toStrictEqual(customAttributeValue);
});
test("Formbricks should track event", async () => {
mockEventTrackResponse();
const mockButton = document.createElement("button");
mockButton.addEventListener("click", async () => {
await formbricks.track("Button Clicked");
});
await mockButton.click();
expect(consoleLogMock).toHaveBeenCalledWith(
expect.stringMatching(/Formbricks: Event "Button Clicked" tracked/)
);
});
test("Formbricks should register for route change", async () => {
mockRegisterRouteChangeResponse();
await formbricks.registerRouteChange();
expect(consoleLogMock).toHaveBeenCalledWith(expect.stringMatching(/Checking page url/));
});
test("Formbricks should reset", async () => {
mockResetResponse();
await formbricks.reset();
const currentStatePerson = formbricks.getPerson();
const currentStatePersonAttributes = currentStatePerson.attributes;
expect(Object.keys(currentStatePersonAttributes).length).toBe(0);
});
*/

View File

@@ -1,8 +0,0 @@
export interface Attribute {
id: string;
value: string;
attributeClass: {
id: string;
name: string;
};
}

View File

@@ -0,0 +1,14 @@
import { PrismaClient } from "@prisma/client";
import { beforeEach, vi } from "vitest";
import { mockDeep, mockReset } from "vitest-mock-extended";
export const prisma = mockDeep<PrismaClient>();
vi.mock("@formbricks/database", () => ({
__esModule: true,
prisma,
}));
beforeEach(() => {
mockReset(prisma);
});

View File

@@ -10,14 +10,22 @@ import { prisma } from "@formbricks/database";
import { createAccount } from "./account/service";
import { verifyPassword } from "./auth/util";
import {
AZUREAD_CLIENT_ID,
AZUREAD_CLIENT_SECRET,
AZUREAD_TENANT_ID,
DEFAULT_TEAM_ID,
DEFAULT_TEAM_ROLE,
EMAIL_VERIFICATION_DISABLED,
GITHUB_ID,
GITHUB_SECRET,
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
OIDC_CLIENT_ID,
OIDC_CLIENT_SECRET,
OIDC_DISPLAY_NAME,
OIDC_ISSUER,
OIDC_SIGNING_ALGORITHM,
} from "./constants";
import { env } from "./env.mjs";
import { verifyToken } from "./jwt";
import { createMembership } from "./membership/service";
import { createProduct } from "./product/service";
@@ -125,18 +133,18 @@ export const authOptions: NextAuthOptions = {
},
}),
GitHubProvider({
clientId: env.GITHUB_ID || "",
clientSecret: env.GITHUB_SECRET || "",
clientId: GITHUB_ID || "",
clientSecret: GITHUB_SECRET || "",
}),
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID || "",
clientSecret: env.GOOGLE_CLIENT_SECRET || "",
clientId: GOOGLE_CLIENT_ID || "",
clientSecret: GOOGLE_CLIENT_SECRET || "",
allowDangerousEmailAccountLinking: true,
}),
AzureAD({
clientId: env.AZUREAD_CLIENT_ID || "",
clientSecret: env.AZUREAD_CLIENT_SECRET || "",
tenantId: env.AZUREAD_TENANT_ID || "",
clientId: AZUREAD_CLIENT_ID || "",
clientSecret: AZUREAD_CLIENT_SECRET || "",
tenantId: AZUREAD_TENANT_ID || "",
}),
{
id: "openid",
@@ -252,16 +260,16 @@ export const authOptions: NextAuthOptions = {
});
// Default team assignment if env variable is set
if (env.DEFAULT_TEAM_ID && env.DEFAULT_TEAM_ID.length > 0) {
if (DEFAULT_TEAM_ID && DEFAULT_TEAM_ID.length > 0) {
// check if team exists
let team = await getTeam(env.DEFAULT_TEAM_ID);
let team = await getTeam(DEFAULT_TEAM_ID);
let isNewTeam = false;
if (!team) {
// create team with id from env
team = await createTeam({ id: env.DEFAULT_TEAM_ID, name: userProfile.name + "'s Team" });
team = await createTeam({ id: DEFAULT_TEAM_ID, name: userProfile.name + "'s Team" });
isNewTeam = true;
}
const role = isNewTeam ? "owner" : env.DEFAULT_TEAM_ROLE || "admin";
const role = isNewTeam ? "owner" : DEFAULT_TEAM_ROLE || "admin";
await createMembership(team.id, userProfile.id, { role, accepted: true });
await createAccount({
...account,

View File

@@ -1 +1 @@
module.exports = { presets: ["@babel/preset-env"] };
module.exports = { presets: [["@babel/preset-env", { targets: { node: "current" } }]] };

View File

@@ -1,6 +1,6 @@
import "server-only";
import { env } from "./env.mjs";
import { env } from "./env";
export const IS_FORMBRICKS_CLOUD = env.IS_FORMBRICKS_CLOUD === "1";
export const REVALIDATION_INTERVAL = 0; //TODO: find a good way to cache and revalidate data when it changes
@@ -80,10 +80,14 @@ export const RESPONSES_PER_PAGE = 10;
export const TEXT_RESPONSES_PER_PAGE = 5;
export const DEFAULT_TEAM_ID = env.DEFAULT_TEAM_ID;
export const DEFAULT_TEAM_ROLE = env.DEFAULT_TEAM_ROLE || "";
export const DEFAULT_TEAM_ROLE = env.DEFAULT_TEAM_ROLE;
export const ONBOARDING_DISABLED = env.ONBOARDING_DISABLED;
// Storage constants
export const S3_ACCESS_KEY = env.S3_ACCESS_KEY;
export const S3_SECRET_KEY = env.S3_SECRET_KEY;
export const S3_REGION = env.S3_REGION;
export const S3_BUCKET_NAME = env.S3_BUCKET_NAME;
export const UPLOADS_DIR = "./uploads";
export const MAX_SIZES = {
public: 1024 * 1024 * 10, // 10MB
@@ -148,9 +152,12 @@ export const SYNC_USER_IDENTIFICATION_RATE_LIMIT = {
allowedPerInterval: 5,
};
export const DEBUG = process.env.DEBUG === "1";
export const DEBUG = env.DEBUG === "1";
// Enterprise License constant
export const ENTERPRISE_LICENSE_KEY = env.ENTERPRISE_LICENSE_KEY;
export const RATE_LIMITING_DISABLED = env.RATE_LIMITING_DISABLED === "1";
export const CUSTOMER_IO_SITE_ID = env.CUSTOMER_IO_SITE_ID;
export const CUSTOMER_IO_API_KEY = env.CUSTOMER_IO_API_KEY;

View File

@@ -1,13 +1,13 @@
import { TUser } from "@formbricks/types/user";
import { env } from "./env.mjs";
import { CUSTOMER_IO_API_KEY, CUSTOMER_IO_SITE_ID } from "./constants";
export const createCustomerIoCustomer = async (user: TUser) => {
if (!env.CUSTOMER_IO_SITE_ID || !env.CUSTOMER_IO_API_KEY) {
if (!CUSTOMER_IO_SITE_ID || !CUSTOMER_IO_API_KEY) {
return;
}
try {
const auth = Buffer.from(`${env.CUSTOMER_IO_SITE_ID}:${env.CUSTOMER_IO_API_KEY}`).toString("base64");
const auth = Buffer.from(`${CUSTOMER_IO_SITE_ID}:${CUSTOMER_IO_API_KEY}`).toString("base64");
const res = await fetch(`https://track-eu.customer.io/api/v1/customers/${user.id}`, {
method: "PUT",
headers: {

View File

@@ -27,7 +27,7 @@ import { formatDateFields } from "../utils/datetime";
import { validateInputs } from "../utils/validate";
import { displayCache } from "./cache";
const selectDisplay = {
export const selectDisplay = {
id: true,
createdAt: true,
updatedAt: true,

View File

@@ -1,10 +1,8 @@
import {
TDisplay,
TDisplayCreateInput,
TDisplayLegacyCreateInput,
TDisplayLegacyUpdateInput,
TDisplayUpdateInput,
} from "@formbricks/types/displays";
import { Prisma } from "@prisma/client";
import { prisma } from "@formbricks/database";
import { selectDisplay } from "../../service";
export const mockEnvironmentId = "clqkr5961000108jyfnjmbjhi";
export const mockSingleUseId = "qj57j3opsw8b5sxgea20fgcq";
@@ -28,49 +26,51 @@ function createMockDisplay(overrides = {}) {
};
}
export const mockDisplay: TDisplay = createMockDisplay();
export const mockDisplay = createMockDisplay();
export const mockDisplayWithPersonId: TDisplay = createMockDisplay({ personId: mockPersonId });
export const mockDisplayWithPersonId = createMockDisplay({ personId: mockPersonId });
export const mockDisplayWithResponseId: TDisplay = createMockDisplay({
export const mockDisplayWithResponseId = createMockDisplay({
personId: mockPersonId,
responseId: mockResponseId,
});
export const mockDisplayInput: TDisplayCreateInput = {
export const mockDisplayInput = {
environmentId: mockEnvironmentId,
surveyId: mockSurveyId,
};
export const mockDisplayInputWithUserId: TDisplayCreateInput = {
export const mockDisplayInputWithUserId = {
...mockDisplayInput,
userId: mockUserId,
};
export const mockDisplayInputWithResponseId: TDisplayCreateInput = {
export const mockDisplayInputWithResponseId = {
...mockDisplayInputWithUserId,
responseId: mockResponseId,
};
export const mockDisplayLegacyInput: TDisplayLegacyCreateInput = {
export const mockDisplayLegacyInput = {
responseId: mockResponseId,
surveyId: mockSurveyId,
};
export const mockDisplayLegacyInputWithPersonId: TDisplayLegacyCreateInput = {
export const mockDisplayLegacyInputWithPersonId = {
...mockDisplayLegacyInput,
personId: mockPersonId,
};
export const mockDisplayUpdate: TDisplayUpdateInput = {
export const mockDisplayUpdate = {
environmentId: mockEnvironmentId,
userId: mockUserId,
responseId: mockResponseId,
};
export const mockDisplayLegacyUpdateInput: TDisplayLegacyUpdateInput = {
export const mockDisplayLegacyUpdateInput = {
personId: mockPersonId,
responseId: mockResponseId,
};
export const mockDisplayLegacyWithRespondedStatus: TDisplay = {
export const mockDisplayLegacyWithRespondedStatus: Prisma.DisplayGetPayload<{
select: typeof selectDisplay;
}> = {
...mockDisplayWithPersonId,
status: "responded",
};

View File

@@ -1,3 +1,4 @@
import { prisma } from "../../__mocks__/database";
import { mockPerson } from "../../response/tests/__mocks__/data.mock";
import {
mockDisplay,
@@ -15,8 +16,8 @@ import {
} from "./__mocks__/data.mock";
import { Prisma } from "@prisma/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { prismaMock } from "@formbricks/database/src/jestClient";
import { DatabaseError, ValidationError } from "@formbricks/types/errors";
import {
@@ -31,6 +32,15 @@ import {
updateDisplayLegacy,
} from "../service";
beforeEach(() => {
vi.resetModules();
vi.resetAllMocks();
});
afterEach(() => {
vi.clearAllMocks();
});
const testInputValidation = async (service: Function, ...args: any[]): Promise<void> => {
it("it should throw a ValidationError if the inputs are invalid", async () => {
await expect(service(...args)).rejects.toThrow(ValidationError);
@@ -38,34 +48,34 @@ const testInputValidation = async (service: Function, ...args: any[]): Promise<v
};
beforeEach(() => {
prismaMock.person.findFirst.mockResolvedValue(mockPerson);
prisma.person.findFirst.mockResolvedValue(mockPerson);
});
describe("Tests for getDisplay", () => {
describe("Happy Path", () => {
it("Returns display associated with a given display ID", async () => {
prismaMock.display.findUnique.mockResolvedValue(mockDisplay);
prisma.display.findUnique.mockResolvedValue(mockDisplay);
const display = await getDisplay(mockDisplay.id);
expect(display).toEqual(mockDisplay);
});
it("Returns all displays associated with a given person ID", async () => {
prismaMock.display.findMany.mockResolvedValue([mockDisplayWithPersonId]);
prisma.display.findMany.mockResolvedValue([mockDisplayWithPersonId]);
const displays = await getDisplaysByPersonId(mockPerson.id);
expect(displays).toEqual([mockDisplayWithPersonId]);
});
it("Returns an empty array when no displays are found for the given person ID", async () => {
prismaMock.display.findMany.mockResolvedValue([]);
prisma.display.findMany.mockResolvedValue([]);
const displays = await getDisplaysByPersonId(mockPerson.id);
expect(displays).toEqual([]);
});
it("Returns display count for the given survey ID", async () => {
prismaMock.display.count.mockResolvedValue(1);
prisma.display.count.mockResolvedValue(1);
const displaCount = await getDisplayCountBySurveyId(mockSurveyId);
expect(displaCount).toEqual(1);
@@ -82,14 +92,14 @@ describe("Tests for getDisplay", () => {
clientVersion: "0.0.1",
});
prismaMock.display.findMany.mockRejectedValue(errToThrow);
prisma.display.findMany.mockRejectedValue(errToThrow);
await expect(getDisplaysByPersonId(mockPerson.id)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.display.findMany.mockRejectedValue(new Error(mockErrorMessage));
prisma.display.findMany.mockRejectedValue(new Error(mockErrorMessage));
await expect(getDisplaysByPersonId(mockPerson.id)).rejects.toThrow(Error);
});
@@ -99,14 +109,14 @@ describe("Tests for getDisplay", () => {
describe("Tests for createDisplay service", () => {
describe("Happy Path", () => {
it("Creates a new display when a userId exists", async () => {
prismaMock.display.create.mockResolvedValue(mockDisplayWithPersonId);
prisma.display.create.mockResolvedValue(mockDisplayWithPersonId);
const display = await createDisplay(mockDisplayInputWithUserId);
expect(display).toEqual(mockDisplayWithPersonId);
});
it("Creates a new display when a userId does not exists", async () => {
prismaMock.display.create.mockResolvedValue(mockDisplay);
prisma.display.create.mockResolvedValue(mockDisplay);
const display = await createDisplay(mockDisplayInput);
expect(display).toEqual(mockDisplay);
@@ -123,14 +133,14 @@ describe("Tests for createDisplay service", () => {
clientVersion: "0.0.1",
});
prismaMock.display.create.mockRejectedValue(errToThrow);
prisma.display.create.mockRejectedValue(errToThrow);
await expect(createDisplay(mockDisplayInputWithUserId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for other exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.display.create.mockRejectedValue(new Error(mockErrorMessage));
prisma.display.create.mockRejectedValue(new Error(mockErrorMessage));
await expect(createDisplay(mockDisplayInput)).rejects.toThrow(Error);
});
@@ -140,7 +150,7 @@ describe("Tests for createDisplay service", () => {
describe("Tests for updateDisplay Service", () => {
describe("Happy Path", () => {
it("Updates a display (responded)", async () => {
prismaMock.display.update.mockResolvedValue(mockDisplayWithResponseId);
prisma.display.update.mockResolvedValue(mockDisplayWithResponseId);
const display = await updateDisplay(mockDisplay.id, mockDisplayUpdate);
expect(display).toEqual(mockDisplayWithResponseId);
@@ -157,14 +167,14 @@ describe("Tests for updateDisplay Service", () => {
clientVersion: "0.0.1",
});
prismaMock.display.update.mockRejectedValue(errToThrow);
prisma.display.update.mockRejectedValue(errToThrow);
await expect(updateDisplay(mockDisplay.id, mockDisplayUpdate)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for other unexpected issues", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.display.update.mockRejectedValue(new Error(mockErrorMessage));
prisma.display.update.mockRejectedValue(new Error(mockErrorMessage));
await expect(updateDisplay(mockDisplay.id, mockDisplayUpdate)).rejects.toThrow(Error);
});
@@ -174,13 +184,13 @@ describe("Tests for updateDisplay Service", () => {
describe("Tests for createDisplayLegacy service", () => {
describe("Happy Path", () => {
it("Creates a display when a person ID exist", async () => {
prismaMock.display.create.mockResolvedValue(mockDisplayWithPersonId);
prisma.display.create.mockResolvedValue(mockDisplayWithPersonId);
const display = await createDisplayLegacy(mockDisplayLegacyInputWithPersonId);
expect(display).toEqual(mockDisplayWithPersonId);
});
it("Creates a display when a person ID does not exist", async () => {
prismaMock.display.create.mockResolvedValue(mockDisplay);
prisma.display.create.mockResolvedValue(mockDisplay);
const display = await createDisplayLegacy(mockDisplayLegacyInput);
expect(display).toEqual(mockDisplay);
@@ -196,14 +206,14 @@ describe("Tests for createDisplayLegacy service", () => {
clientVersion: "0.0.1",
});
prismaMock.display.create.mockRejectedValue(errToThrow);
prisma.display.create.mockRejectedValue(errToThrow);
await expect(createDisplayLegacy(mockDisplayLegacyInputWithPersonId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for other exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.display.create.mockRejectedValue(new Error(mockErrorMessage));
prisma.display.create.mockRejectedValue(new Error(mockErrorMessage));
await expect(createDisplayLegacy(mockDisplayLegacyInputWithPersonId)).rejects.toThrow(Error);
});
@@ -213,14 +223,14 @@ describe("Tests for createDisplayLegacy service", () => {
describe("Tests for updateDisplayLegacy Service", () => {
describe("Happy Path", () => {
it("Updates a display", async () => {
prismaMock.display.update.mockResolvedValue(mockDisplayWithPersonId);
prisma.display.update.mockResolvedValue(mockDisplayWithPersonId);
const display = await updateDisplayLegacy(mockDisplay.id, mockDisplayLegacyUpdateInput);
expect(display).toEqual(mockDisplayWithPersonId);
});
it("marks display as responded legacy", async () => {
prismaMock.display.update.mockResolvedValue(mockDisplayLegacyWithRespondedStatus);
prisma.display.update.mockResolvedValue(mockDisplayLegacyWithRespondedStatus);
const display = await markDisplayRespondedLegacy(mockDisplay.id);
expect(display).toEqual(mockDisplayLegacyWithRespondedStatus);
@@ -237,7 +247,7 @@ describe("Tests for updateDisplayLegacy Service", () => {
clientVersion: "0.0.1",
});
prismaMock.display.update.mockRejectedValue(errToThrow);
prisma.display.update.mockRejectedValue(errToThrow);
await expect(updateDisplayLegacy(mockDisplay.id, mockDisplayLegacyUpdateInput)).rejects.toThrow(
DatabaseError
@@ -246,7 +256,7 @@ describe("Tests for updateDisplayLegacy Service", () => {
it("Throws a generic Error for other unexpected issues", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.display.update.mockRejectedValue(new Error(mockErrorMessage));
prisma.display.update.mockRejectedValue(new Error(mockErrorMessage));
await expect(updateDisplayLegacy(mockDisplay.id, mockDisplayLegacyUpdateInput)).rejects.toThrow(Error);
});
@@ -256,7 +266,7 @@ describe("Tests for updateDisplayLegacy Service", () => {
describe("Tests for deleteDisplayByResponseId service", () => {
describe("Happy Path", () => {
it("Deletes a display when a response associated to it is deleted", async () => {
prismaMock.display.delete.mockResolvedValue(mockDisplayWithResponseId);
prisma.display.delete.mockResolvedValue(mockDisplayWithResponseId);
const display = await deleteDisplayByResponseId(mockResponseId, mockSurveyId);
expect(display).toEqual(mockDisplayWithResponseId);
@@ -272,14 +282,14 @@ describe("Tests for deleteDisplayByResponseId service", () => {
clientVersion: "0.0.1",
});
prismaMock.display.delete.mockRejectedValue(errToThrow);
prisma.display.delete.mockRejectedValue(errToThrow);
await expect(deleteDisplayByResponseId(mockResponseId, mockSurveyId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for other exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.display.delete.mockRejectedValue(new Error(mockErrorMessage));
prisma.display.delete.mockRejectedValue(new Error(mockErrorMessage));
await expect(deleteDisplayByResponseId(mockResponseId, mockSurveyId)).rejects.toThrow(Error);
});

View File

@@ -7,42 +7,31 @@ export const env = createEnv({
* Will throw if you access these variables on the client.
*/
server: {
AIRTABLE_CLIENT_ID: z.string().optional(),
AWS_ACCESS_KEY: z.string().optional(),
AWS_SECRET_KEY: z.string().optional(),
AZUREAD_CLIENT_ID: z.string().optional(),
AZUREAD_CLIENT_SECRET: z.string().optional(),
AZUREAD_TENANT_ID: z.string().optional(),
CRON_SECRET: z.string().optional(),
CUSTOMER_IO_API_KEY: z.string().optional(),
CUSTOMER_IO_SITE_ID: z.string().optional(),
NEXT_PUBLIC_POSTHOG_API_HOST: z.string().optional(),
WEBAPP_URL: z.string().url().optional(),
DATABASE_URL: z.string().url(),
DEBUG: z.enum(["1", "0"]).optional(),
DEFAULT_TEAM_ID: z.string().optional(),
DEFAULT_TEAM_ROLE: z.enum(["owner", "admin", "editor", "developer", "viewer"]).optional(),
EMAIL_AUTH_DISABLED: z.enum(["1", "0"]).optional(),
EMAIL_VERIFICATION_DISABLED: z.enum(["1", "0"]).optional(),
ENCRYPTION_KEY: z.string().length(64).or(z.string().length(32)),
ENTERPRISE_LICENSE_KEY: z.string().optional(),
FORMBRICKS_ENCRYPTION_KEY: z.string().length(24).or(z.string().length(0)).optional(),
NEXTAUTH_SECRET: z.string().min(1),
NEXTAUTH_URL: z.string().url().optional(),
MAIL_FROM: z.string().email().optional(),
SMTP_HOST: z.string().min(1).optional(),
SMTP_PORT: z.string().min(1).optional(),
SMTP_USER: z.string().min(1).optional(),
SMTP_PASSWORD: z.string().min(1).optional(),
SMTP_SECURE_ENABLED: z.enum(["1", "0"]).optional(),
GITHUB_ID: z.string().optional(),
GITHUB_SECRET: z.string().optional(),
GOOGLE_CLIENT_ID: z.string().optional(),
GOOGLE_CLIENT_SECRET: z.string().optional(),
STRIPE_SECRET_KEY: z.string().optional(),
STRIPE_WEBHOOK_SECRET: z.string().optional(),
CRON_SECRET: z.string().optional(),
EMAIL_VERIFICATION_DISABLED: z.enum(["1", "0"]).optional(),
PASSWORD_RESET_DISABLED: z.enum(["1", "0"]).optional(),
SIGNUP_DISABLED: z.enum(["1", "0"]).optional(),
EMAIL_AUTH_DISABLED: z.enum(["1", "0"]).optional(),
PRIVACY_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
TERMS_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
GOOGLE_SHEETS_CLIENT_ID: z.string().optional(),
GOOGLE_SHEETS_CLIENT_SECRET: z.string().optional(),
GOOGLE_SHEETS_REDIRECT_URL: z.string().optional(),
IMPRINT_URL: z
.string()
.url()
@@ -50,33 +39,45 @@ export const env = createEnv({
.or(z.string().refine((str) => str === "")),
INVITE_DISABLED: z.enum(["1", "0"]).optional(),
IS_FORMBRICKS_CLOUD: z.enum(["1", "0"]).optional(),
VERCEL_URL: z.string().optional(),
SHORT_URL_BASE: z.string().url().optional().or(z.string().length(0)),
GOOGLE_SHEETS_CLIENT_ID: z.string().optional(),
GOOGLE_SHEETS_CLIENT_SECRET: z.string().optional(),
GOOGLE_SHEETS_REDIRECT_URL: z.string().optional(),
AIRTABLE_CLIENT_ID: z.string().optional(),
AWS_ACCESS_KEY: z.string().optional(),
AWS_SECRET_KEY: z.string().optional(),
S3_ACCESS_KEY: z.string().optional(),
S3_SECRET_KEY: z.string().optional(),
S3_REGION: z.string().optional(),
S3_BUCKET_NAME: z.string().optional(),
MAIL_FROM: z.string().email().optional(),
NEXTAUTH_SECRET: z.string().min(1),
NEXTAUTH_URL: z.string().url().optional(),
NOTION_OAUTH_CLIENT_ID: z.string().optional(),
NOTION_OAUTH_CLIENT_SECRET: z.string().optional(),
AZUREAD_CLIENT_SECRET: z.string().optional(),
AZUREAD_TENANT_ID: z.string().optional(),
AZUREAD_CLIENT_ID: z.string().optional(),
DEFAULT_TEAM_ID: z.string().optional(),
DEFAULT_TEAM_ROLE: z.enum(["owner", "admin", "editor", "developer", "viewer"]).optional(),
ONBOARDING_DISABLED: z.string().optional(),
ENTERPRISE_LICENSE_KEY: z.string().optional(),
RATE_LIMITING_DISABLED: z.enum(["1", "0"]).optional(),
OIDC_DISPLAY_NAME: z.string().optional(),
OIDC_CLIENT_ID: z.string().optional(),
OIDC_CLIENT_SECRET: z.string().optional(),
OIDC_DISPLAY_NAME: z.string().optional(),
OIDC_ISSUER: z.string().optional(),
OIDC_SIGNING_ALGORITHM: z.string().optional(),
ONBOARDING_DISABLED: z.string().optional(),
PASSWORD_RESET_DISABLED: z.enum(["1", "0"]).optional(),
PRIVACY_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
RATE_LIMITING_DISABLED: z.enum(["1", "0"]).optional(),
S3_ACCESS_KEY: z.string().optional(),
S3_BUCKET_NAME: z.string().optional(),
S3_REGION: z.string().optional(),
S3_SECRET_KEY: z.string().optional(),
SHORT_URL_BASE: z.string().url().optional().or(z.string().length(0)),
SIGNUP_DISABLED: z.enum(["1", "0"]).optional(),
SMTP_HOST: z.string().min(1).optional(),
SMTP_PASSWORD: z.string().min(1).optional(),
SMTP_PORT: z.string().min(1).optional(),
SMTP_SECURE_ENABLED: z.enum(["1", "0"]).optional(),
SMTP_USER: z.string().min(1).optional(),
STRIPE_SECRET_KEY: z.string().optional(),
STRIPE_WEBHOOK_SECRET: z.string().optional(),
TELEMETRY_DISABLED: z.enum(["1", "0"]).optional(),
TERMS_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
VERCEL_URL: z.string().optional(),
WEBAPP_URL: z.string().url().optional(),
},
/*
@@ -103,66 +104,70 @@ export const env = createEnv({
* 💡 You'll get type errors if not all variables from `server` & `client` are included here.
*/
runtimeEnv: {
AIRTABLE_CLIENT_ID: process.env.AIRTABLE_CLIENT_ID,
AWS_ACCESS_KEY: process.env.AWS_ACCESS_KEY,
AWS_SECRET_KEY: process.env.AWS_SECRET_KEY,
AZUREAD_CLIENT_ID: process.env.AZUREAD_CLIENT_ID,
AZUREAD_CLIENT_SECRET: process.env.AZUREAD_CLIENT_SECRET,
AZUREAD_TENANT_ID: process.env.AZUREAD_TENANT_ID,
CRON_SECRET: process.env.CRON_SECRET,
CUSTOMER_IO_API_KEY: process.env.CUSTOMER_IO_API_KEY,
CUSTOMER_IO_SITE_ID: process.env.CUSTOMER_IO_SITE_ID,
WEBAPP_URL: process.env.WEBAPP_URL,
DATABASE_URL: process.env.DATABASE_URL,
DEBUG: process.env.DEBUG,
DEFAULT_TEAM_ID: process.env.DEFAULT_TEAM_ID,
DEFAULT_TEAM_ROLE: process.env.DEFAULT_TEAM_ROLE,
EMAIL_AUTH_DISABLED: process.env.EMAIL_AUTH_DISABLED,
EMAIL_VERIFICATION_DISABLED: process.env.EMAIL_VERIFICATION_DISABLED,
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
MAIL_FROM: process.env.MAIL_FROM,
SMTP_HOST: process.env.SMTP_HOST,
SMTP_PORT: process.env.SMTP_PORT,
SMTP_USER: process.env.SMTP_USER,
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
SMTP_SECURE_ENABLED: process.env.SMTP_SECURE_ENABLED,
ENTERPRISE_LICENSE_KEY: process.env.ENTERPRISE_LICENSE_KEY,
FORMBRICKS_ENCRYPTION_KEY: process.env.FORMBRICKS_ENCRYPTION_KEY,
GITHUB_ID: process.env.GITHUB_ID,
GITHUB_SECRET: process.env.GITHUB_SECRET,
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
CRON_SECRET: process.env.CRON_SECRET,
EMAIL_VERIFICATION_DISABLED: process.env.EMAIL_VERIFICATION_DISABLED,
PASSWORD_RESET_DISABLED: process.env.PASSWORD_RESET_DISABLED,
SIGNUP_DISABLED: process.env.SIGNUP_DISABLED,
EMAIL_AUTH_DISABLED: process.env.EMAIL_AUTH_DISABLED,
INVITE_DISABLED: process.env.INVITE_DISABLED,
PRIVACY_URL: process.env.PRIVACY_URL,
TERMS_URL: process.env.TERMS_URL,
IMPRINT_URL: process.env.IMPRINT_URL,
GOOGLE_SHEETS_CLIENT_ID: process.env.GOOGLE_SHEETS_CLIENT_ID,
GOOGLE_SHEETS_CLIENT_SECRET: process.env.GOOGLE_SHEETS_CLIENT_SECRET,
GOOGLE_SHEETS_REDIRECT_URL: process.env.GOOGLE_SHEETS_REDIRECT_URL,
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
S3_SECRET_KEY: process.env.S3_SECRET_KEY,
S3_REGION: process.env.S3_REGION,
S3_BUCKET_NAME: process.env.S3_BUCKET_NAME,
NOTION_OAUTH_CLIENT_ID: process.env.NOTION_OAUTH_CLIENT_ID,
NOTION_OAUTH_CLIENT_SECRET: process.env.NOTION_OAUTH_CLIENT_SECRET,
IMPRINT_URL: process.env.IMPRINT_URL,
INVITE_DISABLED: process.env.INVITE_DISABLED,
IS_FORMBRICKS_CLOUD: process.env.IS_FORMBRICKS_CLOUD,
MAIL_FROM: process.env.MAIL_FROM,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
NEXT_PUBLIC_FORMBRICKS_API_HOST: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: process.env.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID,
IS_FORMBRICKS_CLOUD: process.env.IS_FORMBRICKS_CLOUD,
NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY,
NEXT_PUBLIC_POSTHOG_API_HOST: process.env.NEXT_PUBLIC_POSTHOG_API_HOST,
FORMBRICKS_ENCRYPTION_KEY: process.env.FORMBRICKS_ENCRYPTION_KEY,
VERCEL_URL: process.env.VERCEL_URL,
SHORT_URL_BASE: process.env.SHORT_URL_BASE,
NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY,
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
AZUREAD_CLIENT_ID: process.env.AZUREAD_CLIENT_ID,
AZUREAD_CLIENT_SECRET: process.env.AZUREAD_CLIENT_SECRET,
AZUREAD_TENANT_ID: process.env.AZUREAD_TENANT_ID,
AIRTABLE_CLIENT_ID: process.env.AIRTABLE_CLIENT_ID,
DEFAULT_TEAM_ID: process.env.DEFAULT_TEAM_ID,
DEFAULT_TEAM_ROLE: process.env.DEFAULT_TEAM_ROLE,
ONBOARDING_DISABLED: process.env.ONBOARDING_DISABLED,
ENTERPRISE_LICENSE_KEY: process.env.ENTERPRISE_LICENSE_KEY,
RATE_LIMITING_DISABLED: process.env.RATE_LIMITING_DISABLED,
OIDC_DISPLAY_NAME: process.env.OIDC_DISPLAY_NAME,
NOTION_OAUTH_CLIENT_ID: process.env.NOTION_OAUTH_CLIENT_ID,
NOTION_OAUTH_CLIENT_SECRET: process.env.NOTION_OAUTH_CLIENT_SECRET,
OIDC_CLIENT_ID: process.env.OIDC_CLIENT_ID,
OIDC_CLIENT_SECRET: process.env.OIDC_CLIENT_SECRET,
OIDC_DISPLAY_NAME: process.env.OIDC_DISPLAY_NAME,
OIDC_ISSUER: process.env.OIDC_ISSUER,
OIDC_SIGNING_ALGORITHM: process.env.OIDC_SIGNING_ALGORITHM,
ONBOARDING_DISABLED: process.env.ONBOARDING_DISABLED,
PASSWORD_RESET_DISABLED: process.env.PASSWORD_RESET_DISABLED,
PRIVACY_URL: process.env.PRIVACY_URL,
RATE_LIMITING_DISABLED: process.env.RATE_LIMITING_DISABLED,
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
S3_BUCKET_NAME: process.env.S3_BUCKET_NAME,
S3_REGION: process.env.S3_REGION,
S3_SECRET_KEY: process.env.S3_SECRET_KEY,
SHORT_URL_BASE: process.env.SHORT_URL_BASE,
SIGNUP_DISABLED: process.env.SIGNUP_DISABLED,
SMTP_HOST: process.env.SMTP_HOST,
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
SMTP_PORT: process.env.SMTP_PORT,
SMTP_SECURE_ENABLED: process.env.SMTP_SECURE_ENABLED,
SMTP_USER: process.env.SMTP_USER,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
TELEMETRY_DISABLED: process.env.TELEMETRY_DISABLED,
TERMS_URL: process.env.TERMS_URL,
VERCEL_URL: process.env.VERCEL_URL,
WEBAPP_URL: process.env.WEBAPP_URL,
},
});

View File

@@ -1,39 +0,0 @@
import { Config } from "jest";
const config: Config = {
preset: "ts-jest",
collectCoverage: true,
// on node 14.x coverage provider v8 offers good speed and more or less good report
coverageProvider: "v8",
testMatch: ["**/*.unit.ts"],
collectCoverageFrom: [
"**/*.{js,jsx,ts,tsx}",
"!**/*.d.ts",
"!**/node_modules/**",
"!<rootDir>/out/**",
"!<rootDir>/.next/**",
"!<rootDir>/*.config.js",
"!<rootDir>/*.config.ts",
"!<rootDir>/coverage/**",
"!<rootDir>/jest/**",
],
testPathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/.next/"],
setupFilesAfterEnv: ["<rootDir>/jest/jestSetup.ts", "<rootDir>/../database/src/jestClient.ts"],
transform: {
// Use babel-jest to transpile tests with the next/babel preset
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
"^.+\\.(ts|tsx)$": "ts-jest",
"^.+\\.(js|jsx)$": ["babel-jest", { presets: ["next/babel"] }],
"^.+\\.mjs$": "babel-jest",
},
transformIgnorePatterns: [
"/node_modules/",
"^.+\\.module\\.(css|sass|scss)$",
"node_modules/(?!uuid).+\\.js$",
],
moduleNameMapper: {
"^uuid$": "uuid",
},
};
export default config;

View File

@@ -2,7 +2,7 @@ import jwt, { JwtPayload } from "jsonwebtoken";
import { prisma } from "@formbricks/database";
import { env } from "./env.mjs";
import { env } from "./env";
export function createToken(userId: string, userEmail: string, options = {}): string {
return jwt.sign({ id: userId }, env.NEXTAUTH_SECRET + userEmail, options);

View File

@@ -10,27 +10,27 @@
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
"lint:report": "eslint . --format json --output-file ../../lint-results/app-store.json",
"test:dev": "jest --coverage --watch",
"test": "jest -ci --coverage --no-cache --silent"
"test:dev": "vitest",
"test": "dotenv -e ../../.env -- vitest run"
},
"dependencies": {
"@aws-sdk/s3-presigned-post": "3.499.0",
"@aws-sdk/client-s3": "3.499.0",
"@aws-sdk/s3-request-presigner": "3.499.0",
"@t3-oss/env-nextjs": "^0.8.0",
"@aws-sdk/client-s3": "3.521.0",
"@aws-sdk/s3-presigned-post": "3.521.0",
"@aws-sdk/s3-request-presigner": "3.521.0",
"@formbricks/api": "*",
"@formbricks/database": "*",
"@formbricks/types": "*",
"@paralleldrive/cuid2": "^2.2.2",
"aws-crt": "^1.21.0",
"@t3-oss/env-nextjs": "^0.9.2",
"aws-crt": "^1.21.1",
"date-fns": "^3.3.1",
"jsonwebtoken": "^9.0.2",
"markdown-it": "^14.0.0",
"mime-types": "^2.1.35",
"nanoid": "^5.0.4",
"next-auth": "^4.24.5",
"nodemailer": "^6.9.8",
"posthog-node": "^3.6.0",
"nanoid": "^5.0.6",
"next-auth": "^4.24.6",
"nodemailer": "^6.9.10",
"posthog-node": "^3.6.3",
"server-only": "^0.0.1",
"tailwind-merge": "^2.2.1"
},
@@ -38,11 +38,10 @@
"@formbricks/tsconfig": "*",
"@types/jsonwebtoken": "^9.0.5",
"@types/mime-types": "^2.1.4",
"babel-jest": "^29.7.0",
"dotenv": "^16.4.5",
"eslint-config-formbricks": "workspace:*",
"jest": "^29.7.0",
"jest-mock-extended": "^3.0.5",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2"
"ts-node": "^10.9.2",
"vitest": "^1.3.1",
"vitest-mock-extended": "^1.3.1"
}
}

View File

@@ -1,9 +1,11 @@
import { PostHog } from "posthog-node";
import { env } from "./env";
const enabled =
process.env.NODE_ENV === "production" &&
process.env.NEXT_PUBLIC_POSTHOG_API_HOST &&
process.env.NEXT_PUBLIC_POSTHOG_API_KEY;
env.NEXT_PUBLIC_POSTHOG_API_HOST &&
env.NEXT_PUBLIC_POSTHOG_API_KEY;
export const capturePosthogEnvironmentEvent = async (
environmentId: string,
@@ -12,14 +14,14 @@ export const capturePosthogEnvironmentEvent = async (
) => {
if (
!enabled ||
typeof process.env.NEXT_PUBLIC_POSTHOG_API_HOST !== "string" ||
typeof process.env.NEXT_PUBLIC_POSTHOG_API_KEY !== "string"
typeof env.NEXT_PUBLIC_POSTHOG_API_HOST !== "string" ||
typeof env.NEXT_PUBLIC_POSTHOG_API_KEY !== "string"
) {
return;
}
try {
const client = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_API_KEY, {
host: process.env.NEXT_PUBLIC_POSTHOG_API_HOST,
const client = new PostHog(env.NEXT_PUBLIC_POSTHOG_API_KEY, {
host: env.NEXT_PUBLIC_POSTHOG_API_HOST,
});
client.capture({
distinctId: environmentId,

View File

@@ -24,7 +24,7 @@ import {
} from "@formbricks/types/responses";
import { TTag } from "@formbricks/types/tags";
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL, WEBAPP_URL } from "../constants";
import { deleteDisplayByResponseId } from "../display/service";
import { createPerson, getPerson, getPersonByUserId, transformPrismaPerson } from "../person/service";
import {
@@ -576,7 +576,7 @@ export const getResponseDownloadUrl = async (
await putFile(fileName, fileBuffer, accessType, environmentId);
return `${process.env.WEBAPP_URL}/storage/${environmentId}/${accessType}/${fileName}`;
return `${WEBAPP_URL}/storage/${environmentId}/${accessType}/${fileName}`;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);

View File

@@ -1,3 +1,4 @@
import { prisma } from "../../__mocks__/database";
import {
getFilteredMockResponses,
getMockUpdateResponseInput,
@@ -19,8 +20,8 @@ import {
} from "./__mocks__/data.mock";
import { Prisma } from "@prisma/client";
import { beforeEach, describe, expect, it } from "vitest";
import { prismaMock } from "@formbricks/database/src/jestClient";
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
import {
TResponse,
@@ -31,7 +32,7 @@ import {
import { TTag } from "@formbricks/types/tags";
import { selectPerson, transformPrismaPerson } from "../../person/service";
import { mockSurveyOutput } from "../../survey/tests/survey.mock";
import { mockSurveyOutput } from "../../survey/tests/__mock__/survey.mock";
import {
createResponse,
createResponseLegacy,
@@ -87,7 +88,7 @@ const createMockResponseLegacyInput = (personId?: string): TResponseLegacyInput
beforeEach(() => {
// @ts-expect-error
prismaMock.response.create.mockImplementation(async (args) => {
prisma.response.create.mockImplementation(async (args) => {
if (args.data.person && args.data.person.connect) {
return {
...mockResponse,
@@ -99,13 +100,13 @@ beforeEach(() => {
});
// mocking the person findFirst call as it is used in the transformPrismaPerson function
prismaMock.person.findFirst.mockResolvedValue(mockPerson);
prismaMock.responseNote.findMany.mockResolvedValue([mockResponseNote]);
prisma.person.findFirst.mockResolvedValue(mockPerson);
prisma.responseNote.findMany.mockResolvedValue([mockResponseNote]);
prismaMock.response.findUnique.mockResolvedValue(mockResponse);
prisma.response.findUnique.mockResolvedValue(mockResponse);
// @ts-expect-error
prismaMock.response.update.mockImplementation(async (args) => {
prisma.response.update.mockImplementation(async (args) => {
if (args.data.finished === true) {
return {
...mockResponse,
@@ -121,12 +122,12 @@ beforeEach(() => {
};
});
prismaMock.response.findMany.mockResolvedValue([mockResponse]);
prismaMock.response.delete.mockResolvedValue(mockResponse);
prisma.response.findMany.mockResolvedValue([mockResponse]);
prisma.response.delete.mockResolvedValue(mockResponse);
prismaMock.display.delete.mockResolvedValue({ ...mockDisplay, status: "seen" });
prisma.display.delete.mockResolvedValue({ ...mockDisplay, status: "seen" });
prismaMock.response.count.mockResolvedValue(1);
prisma.response.count.mockResolvedValue(1);
});
// utility function to test input validation for all services
@@ -139,14 +140,14 @@ const testInputValidation = async (service: Function, ...args: any[]): Promise<v
describe("Tests for getResponsesByPersonId", () => {
describe("Happy Path", () => {
it("Returns all responses associated with a given person ID", async () => {
prismaMock.response.findMany.mockResolvedValue([mockResponseWithMockPerson]);
prisma.response.findMany.mockResolvedValue([mockResponseWithMockPerson]);
const responses = await getResponsesByPersonId(mockPerson.id);
expect(responses).toEqual([expectedResponseWithPerson]);
});
it("Returns an empty array when no responses are found for the given person ID", async () => {
prismaMock.response.findMany.mockResolvedValue([]);
prisma.response.findMany.mockResolvedValue([]);
const responses = await getResponsesByPersonId(mockPerson.id);
expect(responses).toEqual([]);
@@ -163,14 +164,14 @@ describe("Tests for getResponsesByPersonId", () => {
clientVersion: "0.0.1",
});
prismaMock.response.findMany.mockRejectedValue(errToThrow);
prisma.response.findMany.mockRejectedValue(errToThrow);
await expect(getResponsesByPersonId(mockPerson.id)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
await expect(getResponsesByPersonId(mockPerson.id)).rejects.toThrow(Error);
});
@@ -195,14 +196,14 @@ describe("Tests for getResponsesBySingleUseId", () => {
clientVersion: "0.0.1",
});
prismaMock.response.findUnique.mockRejectedValue(errToThrow);
prisma.response.findUnique.mockRejectedValue(errToThrow);
await expect(getResponseBySingleUseId(mockSurveyId, mockSingleUseId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for other exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.findUnique.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.findUnique.mockRejectedValue(new Error(mockErrorMessage));
await expect(getResponseBySingleUseId(mockSurveyId, mockSingleUseId)).rejects.toThrow(Error);
});
@@ -222,13 +223,13 @@ describe("Tests for createResponse service", () => {
});
it("Creates a new person and response when the person does not exist", async () => {
prismaMock.person.findFirst.mockResolvedValue(null);
prismaMock.person.create.mockResolvedValue(mockPerson);
prisma.person.findFirst.mockResolvedValue(null);
prisma.person.create.mockResolvedValue(mockPerson);
const response = await createResponse(mockResponseInputWithUserId);
expect(response).toEqual(expectedResponseWithPerson);
expect(prismaMock.person.create).toHaveBeenCalledWith({
expect(prisma.person.create).toHaveBeenCalledWith({
data: {
environment: { connect: { id: mockEnvironmentId } },
userId: mockUserId,
@@ -251,14 +252,14 @@ describe("Tests for createResponse service", () => {
clientVersion: "0.0.1",
});
prismaMock.response.create.mockRejectedValue(errToThrow);
prisma.response.create.mockRejectedValue(errToThrow);
await expect(createResponse(mockResponseInputWithUserId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for other exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.create.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.create.mockRejectedValue(new Error(mockErrorMessage));
await expect(createResponse(mockResponseInputWithUserId)).rejects.toThrow(Error);
});
@@ -291,7 +292,7 @@ describe("Tests for getResponse service", () => {
testInputValidation(getResponse, "123");
it("Throws ResourceNotFoundError if no response is found", async () => {
prismaMock.response.findUnique.mockResolvedValue(null);
prisma.response.findUnique.mockResolvedValue(null);
const response = await getResponse(mockResponse.id);
expect(response).toBeNull();
});
@@ -303,14 +304,14 @@ describe("Tests for getResponse service", () => {
clientVersion: "0.0.1",
});
prismaMock.response.findUnique.mockRejectedValue(errToThrow);
prisma.response.findUnique.mockRejectedValue(errToThrow);
await expect(getResponse(mockResponse.id)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for other unexpected issues", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.findUnique.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.findUnique.mockRejectedValue(new Error(mockErrorMessage));
await expect(getResponse(mockResponse.id)).rejects.toThrow(Error);
});
@@ -320,13 +321,13 @@ describe("Tests for getResponse service", () => {
describe("Tests for getAttributesFromResponses service", () => {
describe("Happy Path", () => {
it("Retrieves all attributes from responses for a given survey ID", async () => {
prismaMock.response.findMany.mockResolvedValue(mockResponsePersonAttributes);
prisma.response.findMany.mockResolvedValue(mockResponsePersonAttributes);
const attributes = await getResponsePersonAttributes(mockSurveyId);
expect(attributes).toEqual(mockPersonAttributesData);
});
it("Returns an empty Object when no responses with attributes are found for the given survey ID", async () => {
prismaMock.response.findMany.mockResolvedValue([]);
prisma.response.findMany.mockResolvedValue([]);
const responses = await getResponsePersonAttributes(mockSurveyId);
expect(responses).toEqual({});
@@ -343,14 +344,14 @@ describe("Tests for getAttributesFromResponses service", () => {
clientVersion: "0.0.1",
});
prismaMock.response.findMany.mockRejectedValue(errToThrow);
prisma.response.findMany.mockRejectedValue(errToThrow);
await expect(getResponsePersonAttributes(mockSurveyId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected problems", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
await expect(getResponsePersonAttributes(mockSurveyId)).rejects.toThrow(Error);
});
@@ -372,7 +373,7 @@ describe("Tests for getResponses service", () => {
let expectedWhereClause: Prisma.ResponseWhereInput | undefined = {};
// @ts-expect-error
prismaMock.response.findMany.mockImplementation(async (args) => {
prisma.response.findMany.mockImplementation(async (args) => {
expectedWhereClause = args?.where;
return getFilteredMockResponses({ finished: true }, false);
});
@@ -411,7 +412,7 @@ describe("Tests for getResponses service", () => {
let expectedWhereClause: Prisma.ResponseWhereInput | undefined = {};
// @ts-expect-error
prismaMock.response.findMany.mockImplementation(async (args) => {
prisma.response.findMany.mockImplementation(async (args) => {
expectedWhereClause = args?.where;
return getFilteredMockResponses(criteria, false);
});
@@ -429,7 +430,7 @@ describe("Tests for getResponses service", () => {
let expectedWhereClause: Prisma.ResponseWhereInput | undefined = {};
// @ts-expect-error
prismaMock.response.findMany.mockImplementation(async (args) => {
prisma.response.findMany.mockImplementation(async (args) => {
expectedWhereClause = args?.where;
return getFilteredMockResponses({ finished: true });
@@ -453,14 +454,14 @@ describe("Tests for getResponses service", () => {
clientVersion: "0.0.1",
});
prismaMock.response.findMany.mockRejectedValue(errToThrow);
prisma.response.findMany.mockRejectedValue(errToThrow);
await expect(getResponses(mockSurveyId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected problems", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
await expect(getResponses(mockSurveyId)).rejects.toThrow(Error);
});
@@ -470,9 +471,9 @@ describe("Tests for getResponses service", () => {
describe("Tests for getResponseDownloadUrl service", () => {
describe("Happy Path", () => {
it("Returns a download URL for the csv response file", async () => {
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prismaMock.response.count.mockResolvedValue(1);
prismaMock.response.findMany.mockResolvedValue([mockResponse]);
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prisma.response.count.mockResolvedValue(1);
prisma.response.findMany.mockResolvedValue([mockResponse]);
const url = await getResponseDownloadUrl(mockSurveyId, "csv");
const fileExtension = url.split(".").pop();
@@ -480,9 +481,9 @@ describe("Tests for getResponseDownloadUrl service", () => {
});
it("Returns a download URL for the xlsx response file", async () => {
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prismaMock.response.count.mockResolvedValue(1);
prismaMock.response.findMany.mockResolvedValue([mockResponse]);
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prisma.response.count.mockResolvedValue(1);
prisma.response.findMany.mockResolvedValue([mockResponse]);
const url = await getResponseDownloadUrl(mockSurveyId, "xlsx", { finished: true });
const fileExtension = url.split(".").pop();
@@ -494,9 +495,9 @@ describe("Tests for getResponseDownloadUrl service", () => {
testInputValidation(getResponseDownloadUrl, mockSurveyId, 123);
it("Throws error if response file is of different format than expected", async () => {
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prismaMock.response.count.mockResolvedValue(1);
prismaMock.response.findMany.mockResolvedValue([mockResponse]);
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prisma.response.count.mockResolvedValue(1);
prisma.response.findMany.mockResolvedValue([mockResponse]);
const url = await getResponseDownloadUrl(mockSurveyId, "csv", { finished: true });
const fileExtension = url.split(".").pop();
@@ -509,8 +510,8 @@ describe("Tests for getResponseDownloadUrl service", () => {
code: "P2002",
clientVersion: "0.0.1",
});
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prismaMock.response.count.mockRejectedValue(errToThrow);
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prisma.response.count.mockRejectedValue(errToThrow);
await expect(getResponseDownloadUrl(mockSurveyId, "csv")).rejects.toThrow(DatabaseError);
});
@@ -522,9 +523,9 @@ describe("Tests for getResponseDownloadUrl service", () => {
clientVersion: "0.0.1",
});
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prismaMock.response.count.mockResolvedValue(1);
prismaMock.response.findMany.mockRejectedValue(errToThrow);
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
prisma.response.count.mockResolvedValue(1);
prisma.response.findMany.mockRejectedValue(errToThrow);
await expect(getResponseDownloadUrl(mockSurveyId, "csv")).rejects.toThrow(DatabaseError);
});
@@ -533,7 +534,7 @@ describe("Tests for getResponseDownloadUrl service", () => {
const mockErrorMessage = "Mock error message";
// error from getSurvey
prismaMock.survey.findUnique.mockRejectedValue(new Error(mockErrorMessage));
prisma.survey.findUnique.mockRejectedValue(new Error(mockErrorMessage));
await expect(getResponseDownloadUrl(mockSurveyId, "xlsx")).rejects.toThrow(Error);
});
@@ -558,14 +559,14 @@ describe("Tests for getResponsesByEnvironmentId", () => {
clientVersion: "0.0.1",
});
prismaMock.response.findMany.mockRejectedValue(errToThrow);
prisma.response.findMany.mockRejectedValue(errToThrow);
await expect(getResponsesByEnvironmentId(mockEnvironmentId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for any other unhandled exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
await expect(getResponsesByEnvironmentId(mockEnvironmentId)).rejects.toThrow(Error);
});
@@ -596,7 +597,7 @@ describe("Tests for updateResponse Service", () => {
testInputValidation(updateResponse, "123", {});
it("Throws ResourceNotFoundError if no response is found", async () => {
prismaMock.response.findUnique.mockResolvedValue(null);
prisma.response.findUnique.mockResolvedValue(null);
await expect(updateResponse(mockResponse.id, getMockUpdateResponseInput())).rejects.toThrow(
ResourceNotFoundError
);
@@ -609,7 +610,7 @@ describe("Tests for updateResponse Service", () => {
clientVersion: "0.0.1",
});
prismaMock.response.update.mockRejectedValue(errToThrow);
prisma.response.update.mockRejectedValue(errToThrow);
await expect(updateResponse(mockResponse.id, getMockUpdateResponseInput())).rejects.toThrow(
DatabaseError
@@ -618,7 +619,7 @@ describe("Tests for updateResponse Service", () => {
it("Throws a generic Error for other unexpected issues", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.update.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.update.mockRejectedValue(new Error(mockErrorMessage));
await expect(updateResponse(mockResponse.id, getMockUpdateResponseInput())).rejects.toThrow(Error);
});
@@ -643,14 +644,14 @@ describe("Tests for deleteResponse service", () => {
clientVersion: "0.0.1",
});
prismaMock.response.delete.mockRejectedValue(errToThrow);
prisma.response.delete.mockRejectedValue(errToThrow);
await expect(deleteResponse(mockResponse.id)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for any unhandled exception during deletion", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.delete.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.delete.mockRejectedValue(new Error(mockErrorMessage));
await expect(deleteResponse(mockResponse.id)).rejects.toThrow(Error);
});
@@ -665,7 +666,7 @@ describe("Tests for getResponseCountBySurveyId service", () => {
});
it("Returns zero count when there are no responses for a given survey ID", async () => {
prismaMock.response.count.mockResolvedValue(0);
prisma.response.count.mockResolvedValue(0);
const count = await getResponseCountBySurveyId(mockSurveyId);
expect(count).toEqual(0);
});
@@ -676,7 +677,7 @@ describe("Tests for getResponseCountBySurveyId service", () => {
it("Throws a generic Error for other unexpected issues", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.response.count.mockRejectedValue(new Error(mockErrorMessage));
prisma.response.count.mockRejectedValue(new Error(mockErrorMessage));
await expect(getResponseCountBySurveyId(mockSurveyId)).rejects.toThrow(Error);
});

View File

@@ -1,3 +1,4 @@
import { prisma } from "../../__mocks__/database";
import {
getMockSegmentFilters,
mockEnvironmentId,
@@ -11,11 +12,11 @@ import {
} from "./__mocks__/segment.mock";
import { Prisma } from "@prisma/client";
import { beforeEach, describe, expect, it } from "vitest";
import { prismaMock } from "@formbricks/database/src/jestClient";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { testInputValidation } from "../../jest/jestSetup";
import { testInputValidation } from "../../vitestSetup";
import {
cloneSegment,
createSegment,
@@ -31,9 +32,9 @@ function addOrSubractDays(date: Date, number: number) {
}
beforeEach(() => {
prismaMock.segment.findUnique.mockResolvedValue(mockSegmentPrisma);
prismaMock.segment.findMany.mockResolvedValue([mockSegmentPrisma]);
prismaMock.segment.update.mockResolvedValue({
prisma.segment.findUnique.mockResolvedValue(mockSegmentPrisma);
prisma.segment.findMany.mockResolvedValue([mockSegmentPrisma]);
prisma.segment.update.mockResolvedValue({
...mockSegmentPrisma,
filters: getMockSegmentFilters("lastMonthCount", 5, "greaterEqual"),
});
@@ -42,7 +43,7 @@ beforeEach(() => {
describe("Tests for evaluateSegment service", () => {
describe("Happy Path", () => {
it("Returns true when the user meets the segment criteria", async () => {
prismaMock.action.count.mockResolvedValue(4);
prisma.action.count.mockResolvedValue(4);
const result = await evaluateSegment(
mockEvaluateSegmentUserData,
getMockSegmentFilters("lastQuarterCount", 5, "lessThan")
@@ -51,7 +52,7 @@ describe("Tests for evaluateSegment service", () => {
});
it("Calculates the action count for the last month", async () => {
prismaMock.action.count.mockResolvedValue(0);
prisma.action.count.mockResolvedValue(0);
const result = await evaluateSegment(
mockEvaluateSegmentUserData,
getMockSegmentFilters("lastMonthCount", 5, "lessThan")
@@ -60,7 +61,7 @@ describe("Tests for evaluateSegment service", () => {
});
it("Calculates the action count for the last week", async () => {
prismaMock.action.count.mockResolvedValue(6);
prisma.action.count.mockResolvedValue(6);
const result = await evaluateSegment(
mockEvaluateSegmentUserData,
getMockSegmentFilters("lastWeekCount", 5, "greaterEqual")
@@ -69,7 +70,7 @@ describe("Tests for evaluateSegment service", () => {
});
it("Calculates the total occurences of action", async () => {
prismaMock.action.count.mockResolvedValue(6);
prisma.action.count.mockResolvedValue(6);
const result = await evaluateSegment(
mockEvaluateSegmentUserData,
getMockSegmentFilters("occuranceCount", 5, "greaterEqual")
@@ -78,7 +79,7 @@ describe("Tests for evaluateSegment service", () => {
});
it("Calculates the last occurence days ago of action", async () => {
prismaMock.action.findFirst.mockResolvedValue({ createdAt: addOrSubractDays(new Date(), 5) } as any);
prisma.action.findFirst.mockResolvedValue({ createdAt: addOrSubractDays(new Date(), 5) } as any);
const result = await evaluateSegment(
mockEvaluateSegmentUserData,
@@ -88,7 +89,7 @@ describe("Tests for evaluateSegment service", () => {
});
it("Calculates the first occurence days ago of action", async () => {
prismaMock.action.findFirst.mockResolvedValue({ createdAt: addOrSubractDays(new Date(), 5) } as any);
prisma.action.findFirst.mockResolvedValue({ createdAt: addOrSubractDays(new Date(), 5) } as any);
const result = await evaluateSegment(
mockEvaluateSegmentUserData,
@@ -100,7 +101,7 @@ describe("Tests for evaluateSegment service", () => {
describe("Sad Path", () => {
it("Returns false when the user does not meet the segment criteria", async () => {
prismaMock.action.count.mockResolvedValue(0);
prisma.action.count.mockResolvedValue(0);
const result = await evaluateSegment(
mockEvaluateSegmentUserData,
getMockSegmentFilters("lastQuarterCount", 5, "greaterThan")
@@ -113,7 +114,7 @@ describe("Tests for evaluateSegment service", () => {
describe("Tests for createSegment service", () => {
describe("Happy Path", () => {
it("Creates a new user segment", async () => {
prismaMock.segment.create.mockResolvedValue(mockSegmentPrisma);
prisma.segment.create.mockResolvedValue(mockSegmentPrisma);
const result = await createSegment(mockSegmentCreateInput);
expect(result).toEqual(mockSegment);
});
@@ -129,14 +130,14 @@ describe("Tests for createSegment service", () => {
clientVersion: "0.0.1",
});
prismaMock.segment.create.mockRejectedValue(errToThrow);
prisma.segment.create.mockRejectedValue(errToThrow);
await expect(createSegment(mockSegmentCreateInput)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.segment.create.mockRejectedValue(new Error(mockErrorMessage));
prisma.segment.create.mockRejectedValue(new Error(mockErrorMessage));
await expect(createSegment(mockSegmentCreateInput)).rejects.toThrow(Error);
});
@@ -161,14 +162,14 @@ describe("Tests for getSegments service", () => {
clientVersion: "0.0.1",
});
prismaMock.segment.findMany.mockRejectedValue(errToThrow);
prisma.segment.findMany.mockRejectedValue(errToThrow);
await expect(getSegments(mockEnvironmentId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.segment.findMany.mockRejectedValue(new Error(mockErrorMessage));
prisma.segment.findMany.mockRejectedValue(new Error(mockErrorMessage));
await expect(getSegments(mockEnvironmentId)).rejects.toThrow(Error);
});
@@ -187,7 +188,7 @@ describe("Tests for getSegment service", () => {
testInputValidation(getSegment, "123");
it("Throws a ResourceNotFoundError error if the user segment does not exist", async () => {
prismaMock.segment.findUnique.mockResolvedValue(null);
prisma.segment.findUnique.mockResolvedValue(null);
await expect(getSegment(mockSegmentId)).rejects.toThrow(ResourceNotFoundError);
});
@@ -198,14 +199,14 @@ describe("Tests for getSegment service", () => {
clientVersion: "0.0.1",
});
prismaMock.segment.findUnique.mockRejectedValue(errToThrow);
prisma.segment.findUnique.mockRejectedValue(errToThrow);
await expect(getSegment(mockSegmentId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.segment.findUnique.mockRejectedValue(new Error(mockErrorMessage));
prisma.segment.findUnique.mockRejectedValue(new Error(mockErrorMessage));
await expect(getSegment(mockSegmentId)).rejects.toThrow(Error);
});
@@ -227,7 +228,7 @@ describe("Tests for updateSegment service", () => {
testInputValidation(updateSegment, "123", {});
it("Throws a ResourceNotFoundError error if the user segment does not exist", async () => {
prismaMock.segment.findUnique.mockResolvedValue(null);
prisma.segment.findUnique.mockResolvedValue(null);
await expect(updateSegment(mockSegmentId, mockSegmentCreateInput)).rejects.toThrow(
ResourceNotFoundError
);
@@ -240,14 +241,14 @@ describe("Tests for updateSegment service", () => {
clientVersion: "0.0.1",
});
prismaMock.segment.update.mockRejectedValue(errToThrow);
prisma.segment.update.mockRejectedValue(errToThrow);
await expect(updateSegment(mockSegmentId, mockSegmentCreateInput)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.segment.update.mockRejectedValue(new Error(mockErrorMessage));
prisma.segment.update.mockRejectedValue(new Error(mockErrorMessage));
await expect(updateSegment(mockSegmentId, mockSegmentCreateInput)).rejects.toThrow(Error);
});
@@ -257,7 +258,7 @@ describe("Tests for updateSegment service", () => {
describe("Tests for deleteSegment service", () => {
describe("Happy Path", () => {
it("Deletes a user segment", async () => {
prismaMock.segment.delete.mockResolvedValue(mockSegmentPrisma);
prisma.segment.delete.mockResolvedValue(mockSegmentPrisma);
const result = await deleteSegment(mockSegmentId);
expect(result).toEqual(mockSegment);
});
@@ -267,7 +268,7 @@ describe("Tests for deleteSegment service", () => {
testInputValidation(deleteSegment, "123");
it("Throws a ResourceNotFoundError error if the user segment does not exist", async () => {
prismaMock.segment.findUnique.mockResolvedValue(null);
prisma.segment.findUnique.mockResolvedValue(null);
await expect(deleteSegment(mockSegmentId)).rejects.toThrow(ResourceNotFoundError);
});
@@ -278,14 +279,14 @@ describe("Tests for deleteSegment service", () => {
clientVersion: "0.0.1",
});
prismaMock.segment.delete.mockRejectedValue(errToThrow);
prisma.segment.delete.mockRejectedValue(errToThrow);
await expect(deleteSegment(mockSegmentId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.segment.delete.mockRejectedValue(new Error(mockErrorMessage));
prisma.segment.delete.mockRejectedValue(new Error(mockErrorMessage));
await expect(deleteSegment(mockSegmentId)).rejects.toThrow(Error);
});
@@ -295,7 +296,7 @@ describe("Tests for deleteSegment service", () => {
describe("Tests for cloneSegment service", () => {
describe("Happy Path", () => {
it("Clones a user segment", async () => {
prismaMock.segment.create.mockResolvedValue({
prisma.segment.create.mockResolvedValue({
...mockSegmentPrisma,
title: `Copy of ${mockSegmentPrisma.title}`,
});
@@ -311,7 +312,7 @@ describe("Tests for cloneSegment service", () => {
testInputValidation(cloneSegment, "123", "123");
it("Throws a ResourceNotFoundError error if the user segment does not exist", async () => {
prismaMock.segment.findUnique.mockResolvedValue(null);
prisma.segment.findUnique.mockResolvedValue(null);
await expect(cloneSegment(mockSegmentId, mockSurveyId)).rejects.toThrow(ResourceNotFoundError);
});
@@ -322,14 +323,14 @@ describe("Tests for cloneSegment service", () => {
clientVersion: "0.0.1",
});
prismaMock.segment.create.mockRejectedValue(errToThrow);
prisma.segment.create.mockRejectedValue(errToThrow);
await expect(cloneSegment(mockSegmentId, mockSurveyId)).rejects.toThrow(DatabaseError);
});
it("Throws a generic Error for unexpected exceptions", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.segment.create.mockRejectedValue(new Error(mockErrorMessage));
prisma.segment.create.mockRejectedValue(new Error(mockErrorMessage));
await expect(cloneSegment(mockSegmentId, mockSurveyId)).rejects.toThrow(Error);
});

View File

@@ -13,31 +13,30 @@ import { add, isAfter, parseISO } from "date-fns";
import { access, mkdir, readFile, rmdir, unlink, writeFile } from "fs/promises";
import { lookup } from "mime-types";
import { unstable_cache } from "next/cache";
import { join } from "path";
import path from "path";
import path, { join } from "path";
import { TAccessType } from "@formbricks/types/storage";
import { IS_S3_CONFIGURED, MAX_SIZES, UPLOADS_DIR, WEBAPP_URL } from "../constants";
import {
IS_S3_CONFIGURED,
MAX_SIZES,
S3_BUCKET_NAME,
S3_REGION,
UPLOADS_DIR,
WEBAPP_URL,
} from "../constants";
import { generateLocalSignedUrl } from "../crypto";
import { env } from "../env.mjs";
import { env } from "../env";
import { storageCache } from "./cache";
// global variables
const AWS_BUCKET_NAME = env.S3_BUCKET_NAME!;
const AWS_REGION = env.S3_REGION!;
const S3_ACCESS_KEY = env.S3_ACCESS_KEY!;
const S3_SECRET_KEY = env.S3_SECRET_KEY!;
// S3Client Singleton
export const s3Client = new S3Client({
credentials: {
accessKeyId: S3_ACCESS_KEY,
secretAccessKey: S3_SECRET_KEY!,
accessKeyId: env.S3_ACCESS_KEY!,
secretAccessKey: env.S3_SECRET_KEY!,
},
region: AWS_REGION!,
region: S3_REGION!,
});
const ensureDirectoryExists = async (dirPath: string) => {
@@ -81,7 +80,7 @@ const getS3SignedUrl = async (fileKey: string): Promise<string> => {
return unstable_cache(
async () => {
const getObjectCommand = new GetObjectCommand({
Bucket: AWS_BUCKET_NAME,
Bucket: S3_BUCKET_NAME,
Key: fileKey,
});
@@ -242,7 +241,7 @@ export const getS3UploadSignedUrl = async (
try {
const { fields, url } = await createPresignedPost(s3Client, {
Expires: 10 * 60, // 10 minutes
Bucket: AWS_BUCKET_NAME,
Bucket: env.S3_BUCKET_NAME!,
Key: `${environmentId}/${accessType}/${fileName}`,
Fields: {
"Content-Type": contentType,
@@ -305,7 +304,7 @@ export const putFile = async (
} else {
const input = {
Body: fileBuffer,
Bucket: AWS_BUCKET_NAME,
Bucket: S3_BUCKET_NAME,
Key: `${environmentId}/${accessType}/${fileName}`,
};
@@ -354,7 +353,7 @@ export const deleteLocalFile = async (filePath: string) => {
export const deleteS3File = async (fileKey: string) => {
const deleteObjectCommand = new DeleteObjectCommand({
Bucket: AWS_BUCKET_NAME,
Bucket: S3_BUCKET_NAME,
Key: fileKey,
});
@@ -370,7 +369,7 @@ export const deleteS3FilesByEnvironmentId = async (environmentId: string) => {
// List all objects in the bucket with the prefix of environmentId
const listObjectsOutput = await s3Client.send(
new ListObjectsCommand({
Bucket: AWS_BUCKET_NAME,
Bucket: S3_BUCKET_NAME,
Prefix: environmentId,
})
);
@@ -388,7 +387,7 @@ export const deleteS3FilesByEnvironmentId = async (environmentId: string) => {
// Delete the objects
await s3Client.send(
new DeleteObjectsCommand({
Bucket: AWS_BUCKET_NAME,
Bucket: S3_BUCKET_NAME,
Delete: {
Objects: objectsToDelete,
},

View File

@@ -13,8 +13,8 @@ import {
import { TTeam } from "@formbricks/types/teams";
import { TUser } from "@formbricks/types/user";
import { selectPerson } from "../../person/service";
import { selectSurvey } from "../service";
import { selectPerson } from "../../../person/service";
import { selectSurvey } from "../../service";
const currentDate = new Date();
const fourDaysAgo = new Date();

View File

@@ -1,8 +1,11 @@
import { Prisma } from "@prisma/client";
import { prisma } from "../../__mocks__/database";
import { Prisma } from "@prisma/client";
import { beforeEach, describe, expect, it } from "vitest";
import { prismaMock } from "@formbricks/database/src/jestClient";
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
import { testInputValidation } from "../../vitestSetup";
import {
createSurvey,
deleteSurvey,
@@ -26,25 +29,18 @@ import {
mockTransformedSurveyOutput,
mockUser,
updateSurveyInput,
} from "./survey.mock";
// utility function to test input validation for all services
const testInputValidation = async (service: Function, ...args: any[]): Promise<void> => {
it("it should throw a ValidationError if the inputs are invalid", async () => {
await expect(service(...args)).rejects.toThrow(ValidationError);
});
};
} from "./__mock__/survey.mock";
describe("Tests for getSurvey", () => {
describe("Happy Path", () => {
it("Returns a survey", async () => {
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
const survey = await getSurvey(mockId);
expect(survey).toEqual(mockTransformedSurveyOutput);
});
it("Returns null if survey is not found", async () => {
prismaMock.survey.findUnique.mockResolvedValueOnce(null);
prisma.survey.findUnique.mockResolvedValueOnce(null);
const survey = await getSurvey(mockId);
expect(survey).toBeNull();
});
@@ -59,13 +55,13 @@ describe("Tests for getSurvey", () => {
code: "P2002",
clientVersion: "0.0.1",
});
prismaMock.survey.findUnique.mockRejectedValue(errToThrow);
prisma.survey.findUnique.mockRejectedValue(errToThrow);
await expect(getSurvey(mockId)).rejects.toThrow(DatabaseError);
});
it("should throw an error if there is an unknown error", async () => {
const mockErrorMessage = "Mock error message";
prismaMock.survey.findUnique.mockRejectedValue(new Error(mockErrorMessage));
prisma.survey.findUnique.mockRejectedValue(new Error(mockErrorMessage));
await expect(getSurvey(mockId)).rejects.toThrow(Error);
});
});
@@ -74,13 +70,13 @@ describe("Tests for getSurvey", () => {
describe("Tests for getSurveysByActionClassId", () => {
describe("Happy Path", () => {
it("Returns an array of surveys for a given actionClassId", async () => {
prismaMock.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
prisma.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
const surveys = await getSurveysByActionClassId(mockId);
expect(surveys).toEqual([mockTransformedSurveyOutput]);
});
it("Returns an empty array if no surveys are found", async () => {
prismaMock.survey.findMany.mockResolvedValueOnce([]);
prisma.survey.findMany.mockResolvedValueOnce([]);
const surveys = await getSurveysByActionClassId(mockId);
expect(surveys).toEqual([]);
});
@@ -91,7 +87,7 @@ describe("Tests for getSurveysByActionClassId", () => {
it("should throw an error if there is an unknown error", async () => {
const mockErrorMessage = "Unknown error occurred";
prismaMock.survey.findMany.mockRejectedValue(new Error(mockErrorMessage));
prisma.survey.findMany.mockRejectedValue(new Error(mockErrorMessage));
await expect(getSurveysByActionClassId(mockId)).rejects.toThrow(Error);
});
});
@@ -100,13 +96,13 @@ describe("Tests for getSurveysByActionClassId", () => {
describe("Tests for getSurveys", () => {
describe("Happy Path", () => {
it("Returns an array of surveys for a given environmentId and page", async () => {
prismaMock.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
prisma.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
const surveys = await getSurveys(mockId);
expect(surveys).toEqual([mockTransformedSurveyOutput]);
});
it("Returns an empty array if no surveys are found", async () => {
prismaMock.survey.findMany.mockResolvedValueOnce([]);
prisma.survey.findMany.mockResolvedValueOnce([]);
const surveys = await getSurveys(mockId);
expect(surveys).toEqual([]);
@@ -123,13 +119,13 @@ describe("Tests for getSurveys", () => {
clientVersion: "0.0.1",
});
prismaMock.survey.findMany.mockRejectedValue(errToThrow);
prisma.survey.findMany.mockRejectedValue(errToThrow);
await expect(getSurveys(mockId)).rejects.toThrow(DatabaseError);
});
it("should throw an error if there is an unknown error", async () => {
const mockErrorMessage = "Unknown error occurred";
prismaMock.survey.findMany.mockRejectedValue(new Error(mockErrorMessage));
prisma.survey.findMany.mockRejectedValue(new Error(mockErrorMessage));
await expect(getSurveys(mockId)).rejects.toThrow(Error);
});
});
@@ -137,12 +133,12 @@ describe("Tests for getSurveys", () => {
describe("Tests for updateSurvey", () => {
beforeEach(() => {
prismaMock.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
prisma.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
});
describe("Happy Path", () => {
it("Updates a survey successfully", async () => {
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prismaMock.survey.update.mockResolvedValueOnce(mockSurveyOutput);
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prisma.survey.update.mockResolvedValueOnce(mockSurveyOutput);
const updatedSurvey = await updateSurvey(updateSurveyInput);
expect(updatedSurvey).toEqual(mockTransformedSurveyOutput);
});
@@ -152,7 +148,7 @@ describe("Tests for updateSurvey", () => {
testInputValidation(updateSurvey, "123");
it("Throws ResourceNotFoundError if the survey does not exist", async () => {
prismaMock.survey.findUnique.mockRejectedValueOnce(
prisma.survey.findUnique.mockRejectedValueOnce(
new ResourceNotFoundError("Survey", updateSurveyInput.id)
);
await expect(updateSurvey(updateSurveyInput)).rejects.toThrow(ResourceNotFoundError);
@@ -164,15 +160,15 @@ describe("Tests for updateSurvey", () => {
code: "P2002",
clientVersion: "0.0.1",
});
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prismaMock.survey.update.mockRejectedValue(errToThrow);
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prisma.survey.update.mockRejectedValue(errToThrow);
await expect(updateSurvey(updateSurveyInput)).rejects.toThrow(DatabaseError);
});
it("should throw an error if there is an unknown error", async () => {
const mockErrorMessage = "Unknown error occurred";
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prismaMock.survey.update.mockRejectedValue(new Error(mockErrorMessage));
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prisma.survey.update.mockRejectedValue(new Error(mockErrorMessage));
await expect(updateSurvey(updateSurveyInput)).rejects.toThrow(Error);
});
});
@@ -181,7 +177,7 @@ describe("Tests for updateSurvey", () => {
describe("Tests for deleteSurvey", () => {
describe("Happy Path", () => {
it("Deletes a survey successfully", async () => {
prismaMock.survey.delete.mockResolvedValueOnce(mockSurveyOutput);
prisma.survey.delete.mockResolvedValueOnce(mockSurveyOutput);
const deletedSurvey = await deleteSurvey(mockId);
expect(deletedSurvey).toEqual(mockSurveyOutput);
});
@@ -192,8 +188,8 @@ describe("Tests for deleteSurvey", () => {
it("should throw an error if there is an unknown error", async () => {
const mockErrorMessage = "Unknown error occurred";
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prismaMock.survey.delete.mockRejectedValue(new Error(mockErrorMessage));
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prisma.survey.delete.mockRejectedValue(new Error(mockErrorMessage));
await expect(deleteSurvey(mockId)).rejects.toThrow(Error);
});
});
@@ -201,14 +197,14 @@ describe("Tests for deleteSurvey", () => {
describe("Tests for createSurvey", () => {
beforeEach(() => {
prismaMock.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
prisma.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
});
describe("Happy Path", () => {
it("Creates a survey successfully", async () => {
prismaMock.survey.create.mockResolvedValueOnce(mockSurveyOutput);
prismaMock.team.findFirst.mockResolvedValueOnce(mockTeamOutput);
prismaMock.user.findMany.mockResolvedValueOnce([
prisma.survey.create.mockResolvedValueOnce(mockSurveyOutput);
prisma.team.findFirst.mockResolvedValueOnce(mockTeamOutput);
prisma.user.findMany.mockResolvedValueOnce([
{
...mockUser,
twoFactorSecret: null,
@@ -219,7 +215,7 @@ describe("Tests for createSurvey", () => {
role: "engineer",
},
]);
prismaMock.user.update.mockResolvedValueOnce({
prisma.user.update.mockResolvedValueOnce({
...mockUser,
twoFactorSecret: null,
backupCodes: null,
@@ -238,7 +234,7 @@ describe("Tests for createSurvey", () => {
it("should throw an error if there is an unknown error", async () => {
const mockErrorMessage = "Unknown error occurred";
prismaMock.survey.delete.mockRejectedValue(new Error(mockErrorMessage));
prisma.survey.delete.mockRejectedValue(new Error(mockErrorMessage));
await expect(createSurvey(mockId, createSurveyInput)).rejects.toThrow(Error);
});
});
@@ -246,13 +242,13 @@ describe("Tests for createSurvey", () => {
describe("Tests for duplicateSurvey", () => {
beforeEach(() => {
prismaMock.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
prisma.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
});
describe("Happy Path", () => {
it("Duplicates a survey successfully", async () => {
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prismaMock.survey.create.mockResolvedValueOnce(mockSurveyOutput);
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
prisma.survey.create.mockResolvedValueOnce(mockSurveyOutput);
const createdSurvey = await duplicateSurvey(mockId, mockId, mockId);
expect(createdSurvey).toEqual(mockSurveyOutput);
});
@@ -262,13 +258,13 @@ describe("Tests for duplicateSurvey", () => {
testInputValidation(duplicateSurvey, "123", "123");
it("Throws ResourceNotFoundError if the survey does not exist", async () => {
prismaMock.survey.findUnique.mockRejectedValueOnce(new ResourceNotFoundError("Survey", mockId));
prisma.survey.findUnique.mockRejectedValueOnce(new ResourceNotFoundError("Survey", mockId));
await expect(duplicateSurvey(mockId, mockId, mockId)).rejects.toThrow(ResourceNotFoundError);
});
it("should throw an error if there is an unknown error", async () => {
const mockErrorMessage = "Unknown error occurred";
prismaMock.survey.create.mockRejectedValue(new Error(mockErrorMessage));
prisma.survey.create.mockRejectedValue(new Error(mockErrorMessage));
await expect(duplicateSurvey(mockId, mockId, mockId)).rejects.toThrow(Error);
});
});
@@ -277,21 +273,21 @@ describe("Tests for duplicateSurvey", () => {
describe("Tests for getSyncedSurveys", () => {
describe("Happy Path", () => {
beforeEach(() => {
prismaMock.product.findFirst.mockResolvedValueOnce(mockProduct);
prismaMock.display.findMany.mockResolvedValueOnce([mockDisplay]);
prismaMock.attributeClass.findMany.mockResolvedValueOnce([mockAttributeClass]);
prisma.product.findFirst.mockResolvedValueOnce(mockProduct);
prisma.display.findMany.mockResolvedValueOnce([mockDisplay]);
prisma.attributeClass.findMany.mockResolvedValueOnce([mockAttributeClass]);
});
it("Returns synced surveys", async () => {
prismaMock.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
prismaMock.person.findUnique.mockResolvedValueOnce(mockPerson);
prisma.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
prisma.person.findUnique.mockResolvedValueOnce(mockPerson);
const surveys = await getSyncSurveys(mockId, mockPerson.id);
expect(surveys).toEqual([mockTransformedSurveyOutput]);
});
it("Returns an empty array if no surveys are found", async () => {
prismaMock.survey.findMany.mockResolvedValueOnce([]);
prismaMock.person.findUnique.mockResolvedValueOnce(mockPerson);
prisma.survey.findMany.mockResolvedValueOnce([]);
prisma.person.findUnique.mockResolvedValueOnce(mockPerson);
const surveys = await getSyncSurveys(mockId, mockPerson.id);
expect(surveys).toEqual([]);
});
@@ -301,15 +297,15 @@ describe("Tests for getSyncedSurveys", () => {
testInputValidation(getSyncSurveys, "123", {});
it("does not find a Product", async () => {
prismaMock.product.findFirst.mockResolvedValueOnce(null);
prisma.product.findFirst.mockResolvedValueOnce(null);
await expect(getSyncSurveys(mockId, mockPerson.id)).rejects.toThrow(Error);
});
it("should throw an error if there is an unknown error", async () => {
const mockErrorMessage = "Unknown error occurred";
prismaMock.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
prismaMock.survey.create.mockRejectedValue(new Error(mockErrorMessage));
prisma.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
prisma.survey.create.mockRejectedValue(new Error(mockErrorMessage));
await expect(getSyncSurveys(mockId, mockPerson.id)).rejects.toThrow(Error);
});
});

View File

@@ -2,13 +2,10 @@
and how we can improve it. All data including the IP address is collected anonymously
and we cannot trace anything back to you or your customers. If you still want to
disable telemetry, set the environment variable TELEMETRY_DISABLED=1 */
import { env } from "./env";
export const captureTelemetry = async (eventName: string, properties = {}) => {
if (
process.env.TELEMETRY_DISABLED !== "1" &&
process.env.NODE_ENV === "production" &&
process.env.INSTANCE_ID
) {
if (env.TELEMETRY_DISABLED !== "1" && process.env.NODE_ENV === "production" && process.env.INSTANCE_ID) {
try {
await fetch("https://eu.posthog.com/capture/", {
method: "POST",

View File

@@ -1,8 +1,18 @@
{
"extends": "@formbricks/tsconfig/js-library.json",
"extends": "@formbricks/tsconfig/nextjs.json",
"include": ["."],
"exclude": ["dist", "build", "node_modules"],
"compilerOptions": {
"downlevelIteration": true
"downlevelIteration": true,
"baseUrl": ".",
"paths": {
"@prisma/client/*": ["@formbricks/database/client/*"]
},
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
}
}

View File

@@ -168,6 +168,8 @@ export const createUser = async (data: TUserCreateInput): Promise<TUser> => {
select: responseSelection,
});
console.log("user", user);
userCache.revalidate({
email: user.email,
id: user.id,

View File

@@ -1,7 +1,7 @@
import cuid2 from "@paralleldrive/cuid2";
import { decryptAES128, symmetricDecrypt, symmetricEncrypt } from "../../lib/crypto";
import { env } from "../../lib/env.mjs";
import { env } from "../../lib/env";
// generate encrypted single use id for the survey
export const generateSurveySingleUseId = (isEncrypted: boolean): string => {

View File

@@ -0,0 +1,7 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
setupFiles: ["./vitestSetup.ts"],
},
});

View File

@@ -1,25 +1,29 @@
// mock these globally used functions
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { ValidationError } from "@formbricks/types/errors";
jest.mock("next/cache", () => ({
vi.mock("next/cache", () => ({
__esModule: true,
unstable_cache: (fn: () => {}) => {
return async () => {
return fn();
};
},
revalidateTag: jest.fn(),
revalidateTag: vi.fn(),
}));
jest.mock("server-only", () => jest.fn());
vi.mock("server-only", () => {
return {};
});
beforeEach(() => {
jest.resetModules();
jest.resetAllMocks();
vi.resetModules();
vi.resetAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
export const testInputValidation = async (service: Function, ...args: any[]): Promise<void> => {

View File

@@ -8,7 +8,7 @@
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"prettier": "^3.2.4",
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.11"
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "@formbricks/surveys",
"license": "MIT",
"version": "1.5.1",
"version": "1.6.0",
"description": "Formbricks-surveys is a helper library to embed surveys into your application",
"homepage": "https://formbricks.com",
"repository": {
@@ -37,24 +37,27 @@
"clean": "rimraf .turbo node_modules dist"
},
"devDependencies": {
"@calcom/embed-snippet": "1.1.2",
"@calcom/embed-snippet": "1.1.3",
"@formbricks/lib": "workspace:*",
"@formbricks/tsconfig": "workspace:*",
"@formbricks/types": "workspace:*",
"@preact/preset-vite": "^2.8.1",
"isomorphic-dompurify": "^2.3.0",
"autoprefixer": "^10.4.17",
"concurrently": "8.2.2",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "1.10.12",
"postcss": "^8.4.33",
"preact": "^10.19.3",
"isomorphic-dompurify": "^2.4.0",
"postcss": "^8.4.35",
"preact": "^10.19.6",
"react-date-picker": "^10.6.0",
"serve": "14.2.1",
"tailwindcss": "^3.4.1",
"terser": "^5.27.0",
"vite": "^5.0.12",
"vite-plugin-dts": "^3.7.2",
"terser": "^5.28.1",
"vite": "^5.1.4",
"vite-plugin-dts": "^3.7.3",
"vite-tsconfig-paths": "^4.3.1"
},
"dependencies": {
"@t3-oss/env-nextjs": "^0.9.2"
}
}

View File

@@ -10,7 +10,10 @@
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.33",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1"
},
"dependencies": {
"@t3-oss/env-nextjs": "^0.9.2"
}
}

View File

@@ -4,6 +4,7 @@
"extends": "./base.json",
"compilerOptions": {
"plugins": [{ "name": "next" }],
"moduleResolution": "bundler",
"allowJs": true,
"declaration": false,
"declarationMap": false,
@@ -14,7 +15,7 @@
"noEmit": true,
"resolveJsonModule": true,
"strict": false,
"target": "es5"
"target": "esnext"
},
"include": ["src", "next-env.d.ts"],
"exclude": ["node_modules"]

View File

@@ -7,9 +7,12 @@
"clean": "rimraf node_modules dist turbo"
},
"devDependencies": {
"@types/node": "20.11.6",
"@types/react": "18.2.48",
"@types/react-dom": "18.2.18",
"@types/node": "20.11.20",
"@types/react": "18.2.58",
"@types/react-dom": "18.2.19",
"typescript": "^5.3.3"
},
"dependencies": {
"@t3-oss/env-nextjs": "^0.9.2"
}
}

View File

@@ -11,6 +11,7 @@
"@formbricks/tsconfig": "workspace:*"
},
"dependencies": {
"@t3-oss/env-nextjs": "^0.9.2",
"zod": "^3.22.4"
}
}

View File

@@ -5,7 +5,7 @@ import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
import { useEffect } from "react";
import { env } from "@formbricks/lib/env.mjs";
import { env } from "@formbricks/lib/env";
const posthogEnabled = env.NEXT_PUBLIC_POSTHOG_API_KEY && env.NEXT_PUBLIC_POSTHOG_API_HOST;

View File

@@ -38,6 +38,7 @@
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@t3-oss/env-nextjs": "^0.9.2",
"boring-avatars": "^1.10.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",

5407
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -106,9 +106,15 @@
"NEXTAUTH_SECRET",
"NEXTAUTH_URL",
"NODE_ENV",
"OIDC_CLIENT_ID",
"OIDC_CLIENT_SECRET",
"OIDC_DISPLAY_NAME",
"OIDC_ISSUER",
"OIDC_SIGNING_ALGORITHM",
"PASSWORD_RESET_DISABLED",
"PLAYWRIGHT_CI",
"PRIVACY_URL",
"RATE_LIMITING_DISABLED",
"S3_ACCESS_KEY",
"S3_SECRET_KEY",
"S3_REGION",

1
vitest.workspace.ts Normal file
View File

@@ -0,0 +1 @@
export default ["packages/*"];