Files
formbricks/apps/web/modules/projects/settings/lib/project.test.ts
2025-11-17 07:33:52 +00:00

187 lines
6.6 KiB
TypeScript

import { Prisma } from "@prisma/client";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { prisma } from "@formbricks/database";
import { logger } from "@formbricks/logger";
import { StorageErrorCode } from "@formbricks/storage";
import { TEnvironment } from "@formbricks/types/environment";
import { DatabaseError, InvalidInputError, ValidationError } from "@formbricks/types/errors";
import { ZProject } from "@formbricks/types/project";
import { createEnvironment } from "@/lib/environment/service";
import { deleteFilesByEnvironmentId } from "@/modules/storage/service";
import { createProject, deleteProject, updateProject } from "./project";
const baseProject = {
id: "p1",
createdAt: new Date(),
updatedAt: new Date(),
name: "Project 1",
organizationId: "org1",
languages: [],
recontactDays: 0,
linkSurveyBranding: false,
inAppSurveyBranding: false,
config: { channel: null, industry: null },
placement: "bottomRight",
clickOutsideClose: false,
darkOverlay: false,
environments: [
{
id: "cmi2sra0j000004l73fvh7lhe",
createdAt: new Date(),
updatedAt: new Date(),
type: "production" as TEnvironment["type"],
projectId: "p1",
appSetupCompleted: false,
},
{
id: "cmi2srt9q000104l7127e67v7",
createdAt: new Date(),
updatedAt: new Date(),
type: "development" as TEnvironment["type"],
projectId: "p1",
appSetupCompleted: false,
},
],
styling: { allowStyleOverwrite: true },
logo: null,
};
vi.mock("@formbricks/database", () => ({
prisma: {
project: {
update: vi.fn(),
create: vi.fn(),
delete: vi.fn(),
},
projectTeam: {
createMany: vi.fn(),
},
},
}));
vi.mock("@formbricks/logger", () => ({
logger: {
error: vi.fn(),
},
}));
vi.mock("@/modules/storage/service", () => ({
deleteFilesByEnvironmentId: vi.fn(),
}));
vi.mock("@/lib/environment/service", () => ({
createEnvironment: vi.fn(),
}));
describe("project lib", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("updateProject", () => {
test("updates project and revalidates cache", async () => {
vi.mocked(prisma.project.update).mockResolvedValueOnce(baseProject as any);
const result = await updateProject("p1", { name: "Project 1", environments: baseProject.environments });
expect(result).toEqual(ZProject.parse(baseProject));
expect(prisma.project.update).toHaveBeenCalled();
});
test("throws DatabaseError on Prisma error", async () => {
vi.mocked(prisma.project.update).mockRejectedValueOnce(
new (class extends Error {
constructor() {
super();
this.message = "fail";
}
})()
);
await expect(updateProject("p1", { name: "Project 1" })).rejects.toThrow();
});
test("throws ValidationError on Zod error", async () => {
vi.mocked(prisma.project.update).mockResolvedValueOnce({ ...baseProject, id: 123 } as any);
await expect(
updateProject("p1", { name: "Project 1", environments: baseProject.environments })
).rejects.toThrow(ValidationError);
});
});
describe("createProject", () => {
test("creates project, environments, and revalidates cache", async () => {
vi.mocked(prisma.project.create).mockResolvedValueOnce({ ...baseProject, id: "p2" } as any);
vi.mocked(prisma.projectTeam.createMany).mockResolvedValueOnce({} as any);
vi.mocked(createEnvironment).mockResolvedValueOnce(baseProject.environments[0] as any);
vi.mocked(createEnvironment).mockResolvedValueOnce(baseProject.environments[1] as any);
vi.mocked(prisma.project.update).mockResolvedValueOnce(baseProject as any);
const result = await createProject("org1", { name: "Project 1", teamIds: ["t1"] });
expect(result).toEqual(baseProject);
expect(prisma.project.create).toHaveBeenCalled();
expect(prisma.projectTeam.createMany).toHaveBeenCalled();
expect(createEnvironment).toHaveBeenCalled();
});
test("throws ValidationError if name is missing", async () => {
await expect(createProject("org1", {})).rejects.toThrow(ValidationError);
});
test("throws InvalidInputError on unique constraint", async () => {
const prismaError = new Prisma.PrismaClientKnownRequestError("Test Prisma Error", {
code: "P2002",
clientVersion: "5.0.0",
});
vi.mocked(prisma.project.create).mockRejectedValueOnce(prismaError);
await expect(createProject("org1", { name: "Project 1" })).rejects.toThrow(InvalidInputError);
});
test("throws DatabaseError on Prisma error", async () => {
const prismaError = new Prisma.PrismaClientKnownRequestError("Test Prisma Error", {
code: "P2001",
clientVersion: "5.0.0",
});
vi.mocked(prisma.project.create).mockRejectedValueOnce(prismaError);
await expect(createProject("org1", { name: "Project 1" })).rejects.toThrow(DatabaseError);
});
test("throws unknown error", async () => {
vi.mocked(prisma.project.create).mockRejectedValueOnce(new Error("fail"));
await expect(createProject("org1", { name: "Project 1" })).rejects.toThrow("fail");
});
});
describe("deleteProject", () => {
test("deletes project, deletes files, and revalidates cache", async () => {
vi.mocked(prisma.project.delete).mockResolvedValueOnce(baseProject as any);
vi.mocked(deleteFilesByEnvironmentId).mockResolvedValue({ ok: true, data: undefined });
const result = await deleteProject("p1");
expect(result).toEqual(baseProject);
expect(deleteFilesByEnvironmentId).toHaveBeenCalledWith("cmi2sra0j000004l73fvh7lhe");
});
test("logs error if file deletion fails", async () => {
vi.mocked(prisma.project.delete).mockResolvedValueOnce(baseProject as any);
vi.mocked(deleteFilesByEnvironmentId).mockResolvedValue({
ok: false,
error: { code: StorageErrorCode.Unknown },
} as any);
vi.mocked(logger.error).mockImplementation(() => {});
await deleteProject("p1");
expect(logger.error).toHaveBeenCalled();
});
test("throws DatabaseError on Prisma error", async () => {
const err = new Prisma.PrismaClientKnownRequestError("Test Prisma Error", {
code: "P2001",
clientVersion: "5.0.0",
});
vi.mocked(prisma.project.delete).mockRejectedValueOnce(err as any);
await expect(deleteProject("p1")).rejects.toThrow(DatabaseError);
});
test("throws unknown error", async () => {
vi.mocked(prisma.project.delete).mockRejectedValueOnce(new Error("fail"));
await expect(deleteProject("p1")).rejects.toThrow("fail");
});
});
});