mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-02 11:30:31 -05:00
fix: unformatted db message in client display api (#6176)
This commit is contained in:
committed by
GitHub
parent
2166c44470
commit
b1a35d4a69
@@ -0,0 +1,222 @@
|
||||
import { validateInputs } from "@/lib/utils/validate";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TDisplayCreateInput } from "@formbricks/types/displays";
|
||||
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
|
||||
import { getContactByUserId } from "./contact";
|
||||
import { createDisplay } from "./display";
|
||||
|
||||
vi.mock("@/lib/utils/validate", () => ({
|
||||
validateInputs: vi.fn((inputs) => inputs.map((input) => input[0])), // Pass through validation for testing
|
||||
}));
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
contact: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
display: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
survey: {
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("./contact", () => ({
|
||||
getContactByUserId: vi.fn(),
|
||||
}));
|
||||
|
||||
const environmentId = "test-env-id";
|
||||
const surveyId = "test-survey-id";
|
||||
const userId = "test-user-id";
|
||||
const contactId = "test-contact-id";
|
||||
const displayId = "test-display-id";
|
||||
|
||||
const displayInput: TDisplayCreateInput = {
|
||||
environmentId,
|
||||
surveyId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const displayInputWithoutUserId: TDisplayCreateInput = {
|
||||
environmentId,
|
||||
surveyId,
|
||||
};
|
||||
|
||||
const mockContact = {
|
||||
id: contactId,
|
||||
environmentId,
|
||||
userId,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const mockDisplay = {
|
||||
id: displayId,
|
||||
contactId,
|
||||
surveyId,
|
||||
responseId: null,
|
||||
status: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const mockDisplayWithoutContact = {
|
||||
id: displayId,
|
||||
contactId: null,
|
||||
surveyId,
|
||||
responseId: null,
|
||||
status: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const mockSurvey = {
|
||||
id: surveyId,
|
||||
name: "Test Survey",
|
||||
environmentId,
|
||||
} as any;
|
||||
|
||||
describe("createDisplay", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(prisma.survey.findUnique).mockResolvedValue(mockSurvey);
|
||||
});
|
||||
|
||||
test("should create a display with existing contact successfully", async () => {
|
||||
vi.mocked(getContactByUserId).mockResolvedValue(mockContact);
|
||||
vi.mocked(prisma.display.create).mockResolvedValue(mockDisplay);
|
||||
|
||||
const result = await createDisplay(displayInput);
|
||||
|
||||
expect(validateInputs).toHaveBeenCalledWith([displayInput, expect.any(Object)]);
|
||||
expect(getContactByUserId).toHaveBeenCalledWith(environmentId, userId);
|
||||
expect(prisma.contact.create).not.toHaveBeenCalled();
|
||||
expect(prisma.display.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
survey: { connect: { id: surveyId } },
|
||||
contact: { connect: { id: contactId } },
|
||||
},
|
||||
select: { id: true, contactId: true, surveyId: true },
|
||||
});
|
||||
expect(result).toEqual(mockDisplay);
|
||||
});
|
||||
|
||||
test("should create a display and new contact when contact does not exist", async () => {
|
||||
vi.mocked(getContactByUserId).mockResolvedValue(null);
|
||||
vi.mocked(prisma.contact.create).mockResolvedValue(mockContact);
|
||||
vi.mocked(prisma.display.create).mockResolvedValue(mockDisplay);
|
||||
|
||||
const result = await createDisplay(displayInput);
|
||||
|
||||
expect(validateInputs).toHaveBeenCalledWith([displayInput, expect.any(Object)]);
|
||||
expect(getContactByUserId).toHaveBeenCalledWith(environmentId, userId);
|
||||
expect(prisma.contact.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
environment: { connect: { id: environmentId } },
|
||||
attributes: {
|
||||
create: {
|
||||
attributeKey: {
|
||||
connect: { key_environmentId: { key: "userId", environmentId } },
|
||||
},
|
||||
value: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(prisma.display.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
survey: { connect: { id: surveyId } },
|
||||
contact: { connect: { id: contactId } },
|
||||
},
|
||||
select: { id: true, contactId: true, surveyId: true },
|
||||
});
|
||||
expect(result).toEqual(mockDisplay);
|
||||
});
|
||||
|
||||
test("should create a display without contact when userId is not provided", async () => {
|
||||
vi.mocked(prisma.display.create).mockResolvedValue(mockDisplayWithoutContact);
|
||||
|
||||
const result = await createDisplay(displayInputWithoutUserId);
|
||||
|
||||
expect(validateInputs).toHaveBeenCalledWith([displayInputWithoutUserId, expect.any(Object)]);
|
||||
expect(getContactByUserId).not.toHaveBeenCalled();
|
||||
expect(prisma.contact.create).not.toHaveBeenCalled();
|
||||
expect(prisma.display.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
survey: { connect: { id: surveyId } },
|
||||
},
|
||||
select: { id: true, contactId: true, surveyId: true },
|
||||
});
|
||||
expect(result).toEqual(mockDisplayWithoutContact);
|
||||
});
|
||||
|
||||
test("should throw ValidationError if validation fails", async () => {
|
||||
const validationError = new ValidationError("Validation failed");
|
||||
vi.mocked(validateInputs).mockImplementation(() => {
|
||||
throw validationError;
|
||||
});
|
||||
|
||||
await expect(createDisplay(displayInput)).rejects.toThrow(ValidationError);
|
||||
expect(getContactByUserId).not.toHaveBeenCalled();
|
||||
expect(prisma.display.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should throw InvalidInputError when survey does not exist (RelatedRecordDoesNotExist)", async () => {
|
||||
vi.mocked(getContactByUserId).mockResolvedValue(mockContact);
|
||||
vi.mocked(prisma.survey.findUnique).mockResolvedValue(null);
|
||||
|
||||
await expect(createDisplay(displayInput)).rejects.toThrow(new ResourceNotFoundError("Survey", surveyId));
|
||||
expect(getContactByUserId).toHaveBeenCalledWith(environmentId, userId);
|
||||
expect(prisma.survey.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: surveyId, environmentId },
|
||||
});
|
||||
expect(prisma.display.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should throw DatabaseError on other Prisma known request errors", async () => {
|
||||
const prismaError = new Prisma.PrismaClientKnownRequestError("Database error", {
|
||||
code: "P2002",
|
||||
clientVersion: "2.0.0",
|
||||
});
|
||||
vi.mocked(getContactByUserId).mockResolvedValue(mockContact);
|
||||
vi.mocked(prisma.display.create).mockRejectedValue(prismaError);
|
||||
|
||||
await expect(createDisplay(displayInput)).rejects.toThrow(DatabaseError);
|
||||
expect(getContactByUserId).toHaveBeenCalledWith(environmentId, userId);
|
||||
expect(prisma.display.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should throw original error on other errors during display creation", async () => {
|
||||
const genericError = new Error("Something went wrong");
|
||||
vi.mocked(getContactByUserId).mockResolvedValue(mockContact);
|
||||
vi.mocked(prisma.display.create).mockRejectedValue(genericError);
|
||||
|
||||
await expect(createDisplay(displayInput)).rejects.toThrow(genericError);
|
||||
expect(getContactByUserId).toHaveBeenCalledWith(environmentId, userId);
|
||||
expect(prisma.display.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should throw error if getContactByUserId fails", async () => {
|
||||
const contactError = new Error("Failed to get contact");
|
||||
vi.mocked(getContactByUserId).mockRejectedValue(contactError);
|
||||
|
||||
await expect(createDisplay(displayInput)).rejects.toThrow(contactError);
|
||||
expect(getContactByUserId).toHaveBeenCalledWith(environmentId, userId);
|
||||
expect(prisma.display.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should throw error if contact creation fails", async () => {
|
||||
const contactCreateError = new Error("Failed to create contact");
|
||||
vi.mocked(getContactByUserId).mockResolvedValue(null);
|
||||
vi.mocked(prisma.contact.create).mockRejectedValue(contactCreateError);
|
||||
|
||||
await expect(createDisplay(displayInput)).rejects.toThrow(contactCreateError);
|
||||
expect(getContactByUserId).toHaveBeenCalledWith(environmentId, userId);
|
||||
expect(prisma.contact.create).toHaveBeenCalled();
|
||||
expect(prisma.display.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import { validateInputs } from "@/lib/utils/validate";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TDisplayCreateInput, ZDisplayCreateInput } from "@formbricks/types/displays";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { getContactByUserId } from "./contact";
|
||||
|
||||
export const createDisplay = async (displayInput: TDisplayCreateInput): Promise<{ id: string }> => {
|
||||
@@ -31,6 +31,16 @@ export const createDisplay = async (displayInput: TDisplayCreateInput): Promise<
|
||||
}
|
||||
}
|
||||
|
||||
const survey = await prisma.survey.findUnique({
|
||||
where: {
|
||||
id: surveyId,
|
||||
environmentId,
|
||||
},
|
||||
});
|
||||
if (!survey) {
|
||||
throw new ResourceNotFoundError("Survey", surveyId);
|
||||
}
|
||||
|
||||
const display = await prisma.display.create({
|
||||
data: {
|
||||
survey: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { capturePosthogEnvironmentEvent } from "@/lib/posthogServer";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZDisplayCreateInput } from "@formbricks/types/displays";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { createDisplay } from "./lib/display";
|
||||
|
||||
interface Context {
|
||||
@@ -52,11 +52,11 @@ export const POST = async (request: Request, context: Context): Promise<Response
|
||||
await capturePosthogEnvironmentEvent(inputValidation.data.environmentId, "display created");
|
||||
return responses.successResponse(response, true);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidInputError) {
|
||||
return responses.badRequestResponse(error.message);
|
||||
if (error instanceof ResourceNotFoundError) {
|
||||
return responses.notFoundResponse("Survey", inputValidation.data.surveyId);
|
||||
} else {
|
||||
logger.error({ error, url: request.url }, "Error in POST /api/v1/client/[environmentId]/displays");
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
return responses.internalServerErrorResponse("Something went wrong. Please try again.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { validateInputs } from "@/lib/utils/validate";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { DatabaseError, ValidationError } from "@formbricks/types/errors";
|
||||
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
|
||||
import { TDisplayCreateInputV2 } from "../types/display";
|
||||
import { doesContactExist } from "./contact";
|
||||
import { createDisplay } from "./display";
|
||||
@@ -16,6 +16,9 @@ vi.mock("@formbricks/database", () => ({
|
||||
display: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
survey: {
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -43,17 +46,32 @@ const mockDisplay = {
|
||||
id: displayId,
|
||||
contactId,
|
||||
surveyId,
|
||||
responseId: null,
|
||||
status: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const mockDisplayWithoutContact = {
|
||||
id: displayId,
|
||||
contactId: null,
|
||||
surveyId,
|
||||
responseId: null,
|
||||
status: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const mockSurvey = {
|
||||
id: surveyId,
|
||||
name: "Test Survey",
|
||||
environmentId,
|
||||
} as any;
|
||||
|
||||
describe("createDisplay", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(prisma.survey.findUnique).mockResolvedValue(mockSurvey);
|
||||
});
|
||||
|
||||
test("should create a display with contactId successfully", async () => {
|
||||
@@ -119,7 +137,19 @@ describe("createDisplay", () => {
|
||||
expect(prisma.display.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should throw DatabaseError on Prisma known request error", async () => {
|
||||
test("should throw InvalidInputError when survey does not exist (P2025)", async () => {
|
||||
vi.mocked(doesContactExist).mockResolvedValue(true);
|
||||
vi.mocked(prisma.survey.findUnique).mockResolvedValue(null);
|
||||
|
||||
await expect(createDisplay(displayInput)).rejects.toThrow(new ResourceNotFoundError("Survey", surveyId));
|
||||
expect(doesContactExist).toHaveBeenCalledWith(contactId);
|
||||
expect(prisma.survey.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: surveyId, environmentId },
|
||||
});
|
||||
expect(prisma.display.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should throw DatabaseError on other Prisma known request errors", async () => {
|
||||
const prismaError = new Prisma.PrismaClientKnownRequestError("DB error", {
|
||||
code: "P2002",
|
||||
clientVersion: "2.0.0",
|
||||
|
||||
@@ -5,17 +5,27 @@ import {
|
||||
import { validateInputs } from "@/lib/utils/validate";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { doesContactExist } from "./contact";
|
||||
|
||||
export const createDisplay = async (displayInput: TDisplayCreateInputV2): Promise<{ id: string }> => {
|
||||
validateInputs([displayInput, ZDisplayCreateInputV2]);
|
||||
|
||||
const { contactId, surveyId } = displayInput;
|
||||
const { contactId, surveyId, environmentId } = displayInput;
|
||||
|
||||
try {
|
||||
const contactExists = contactId ? await doesContactExist(contactId) : false;
|
||||
|
||||
const survey = await prisma.survey.findUnique({
|
||||
where: {
|
||||
id: surveyId,
|
||||
environmentId,
|
||||
},
|
||||
});
|
||||
if (!survey) {
|
||||
throw new ResourceNotFoundError("Survey", surveyId);
|
||||
}
|
||||
|
||||
const display = await prisma.display.create({
|
||||
data: {
|
||||
survey: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { capturePosthogEnvironmentEvent } from "@/lib/posthogServer";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { createDisplay } from "./lib/display";
|
||||
|
||||
interface Context {
|
||||
@@ -52,11 +52,11 @@ export const POST = async (request: Request, context: Context): Promise<Response
|
||||
await capturePosthogEnvironmentEvent(inputValidation.data.environmentId, "display created");
|
||||
return responses.successResponse(response, true);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidInputError) {
|
||||
return responses.badRequestResponse(error.message);
|
||||
if (error instanceof ResourceNotFoundError) {
|
||||
return responses.notFoundResponse("Survey", inputValidation.data.surveyId);
|
||||
} else {
|
||||
logger.error({ error, url: request.url }, "Error creating display");
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
return responses.internalServerErrorResponse("Something went wrong. Please try again.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
mockDisplayInputWithUserId,
|
||||
mockDisplayWithPersonId,
|
||||
mockEnvironment,
|
||||
mockEnvironmentId,
|
||||
mockSurveyId,
|
||||
} from "./__mocks__/data.mock";
|
||||
import { prisma } from "@/lib/__mocks__/database";
|
||||
import { createDisplay } from "@/app/api/v1/client/[environmentId]/displays/lib/display";
|
||||
@@ -26,20 +28,25 @@ afterEach(() => {
|
||||
|
||||
beforeEach(() => {
|
||||
prisma.contact.findFirst.mockResolvedValue(mockContact);
|
||||
prisma.survey.findUnique.mockResolvedValue({
|
||||
id: mockSurveyId,
|
||||
name: "Test Survey",
|
||||
environmentId: mockEnvironmentId,
|
||||
} as any);
|
||||
});
|
||||
|
||||
describe("Tests for createDisplay service", () => {
|
||||
describe("Happy Path", () => {
|
||||
test("Creates a new display when a userId exists", async () => {
|
||||
prisma.environment.findUnique.mockResolvedValue(mockEnvironment);
|
||||
prisma.display.create.mockResolvedValue(mockDisplayWithPersonId);
|
||||
prisma.environment.findUnique.mockResolvedValue(mockEnvironment as any);
|
||||
prisma.display.create.mockResolvedValue(mockDisplayWithPersonId as any);
|
||||
|
||||
const display = await createDisplay(mockDisplayInputWithUserId);
|
||||
expect(display).toEqual(mockDisplayWithPersonId);
|
||||
});
|
||||
|
||||
test("Creates a new display when a userId does not exists", async () => {
|
||||
prisma.display.create.mockResolvedValue(mockDisplay);
|
||||
prisma.display.create.mockResolvedValue(mockDisplay as any);
|
||||
|
||||
const display = await createDisplay(mockDisplayInput);
|
||||
expect(display).toEqual(mockDisplay);
|
||||
@@ -51,7 +58,7 @@ describe("Tests for createDisplay service", () => {
|
||||
|
||||
test("Throws DatabaseError on PrismaClientKnownRequestError occurrence", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prisma.environment.findUnique.mockResolvedValue(mockEnvironment);
|
||||
prisma.environment.findUnique.mockResolvedValue(mockEnvironment as any);
|
||||
const errToThrow = new Prisma.PrismaClientKnownRequestError(mockErrorMessage, {
|
||||
code: PrismaErrorType.UniqueConstraintViolation,
|
||||
clientVersion: "0.0.1",
|
||||
@@ -74,7 +81,7 @@ describe("Tests for createDisplay service", () => {
|
||||
describe("Tests for delete display service", () => {
|
||||
describe("Happy Path", () => {
|
||||
test("Deletes a display", async () => {
|
||||
prisma.display.delete.mockResolvedValue(mockDisplay);
|
||||
prisma.display.delete.mockResolvedValue(mockDisplay as any);
|
||||
|
||||
const display = await deleteDisplay(mockDisplay.id);
|
||||
expect(display).toEqual(mockDisplay);
|
||||
|
||||
Reference in New Issue
Block a user