mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-23 14:40:44 -06:00
Compare commits
2 Commits
fix/sdk-ra
...
v3.15.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a59dc8c109 | ||
|
|
975a3a2157 |
@@ -8,7 +8,7 @@ import { TUser } from "@formbricks/types/user";
|
|||||||
import Page from "./page";
|
import Page from "./page";
|
||||||
|
|
||||||
vi.mock("@/lib/project/service", () => ({
|
vi.mock("@/lib/project/service", () => ({
|
||||||
getProjectEnvironmentsByOrganizationIds: vi.fn(),
|
getUserProjectEnvironmentsByOrganizationIds: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@/lib/instance/service", () => ({
|
vi.mock("@/lib/instance/service", () => ({
|
||||||
@@ -152,7 +152,7 @@ describe("Page", () => {
|
|||||||
const { getIsFreshInstance } = await import("@/lib/instance/service");
|
const { getIsFreshInstance } = await import("@/lib/instance/service");
|
||||||
const { getUser } = await import("@/lib/user/service");
|
const { getUser } = await import("@/lib/user/service");
|
||||||
const { getOrganizationsByUserId } = await import("@/lib/organization/service");
|
const { getOrganizationsByUserId } = await import("@/lib/organization/service");
|
||||||
const { getProjectEnvironmentsByOrganizationIds } = await import("@/lib/project/service");
|
const { getUserProjectEnvironmentsByOrganizationIds } = await import("@/lib/project/service");
|
||||||
const { getMembershipByUserIdOrganizationId } = await import("@/lib/membership/service");
|
const { getMembershipByUserIdOrganizationId } = await import("@/lib/membership/service");
|
||||||
const { getAccessFlags } = await import("@/lib/membership/utils");
|
const { getAccessFlags } = await import("@/lib/membership/utils");
|
||||||
const { redirect } = await import("next/navigation");
|
const { redirect } = await import("next/navigation");
|
||||||
@@ -220,7 +220,7 @@ describe("Page", () => {
|
|||||||
} as any);
|
} as any);
|
||||||
vi.mocked(getIsFreshInstance).mockResolvedValue(false);
|
vi.mocked(getIsFreshInstance).mockResolvedValue(false);
|
||||||
vi.mocked(getUser).mockResolvedValue(mockUser);
|
vi.mocked(getUser).mockResolvedValue(mockUser);
|
||||||
vi.mocked(getProjectEnvironmentsByOrganizationIds).mockResolvedValue(
|
vi.mocked(getUserProjectEnvironmentsByOrganizationIds).mockResolvedValue(
|
||||||
mockUserProjects as unknown as TProject[]
|
mockUserProjects as unknown as TProject[]
|
||||||
);
|
);
|
||||||
vi.mocked(getOrganizationsByUserId).mockResolvedValue([mockOrganization]);
|
vi.mocked(getOrganizationsByUserId).mockResolvedValue([mockOrganization]);
|
||||||
@@ -241,7 +241,7 @@ describe("Page", () => {
|
|||||||
const { getServerSession } = await import("next-auth");
|
const { getServerSession } = await import("next-auth");
|
||||||
const { getIsFreshInstance } = await import("@/lib/instance/service");
|
const { getIsFreshInstance } = await import("@/lib/instance/service");
|
||||||
const { getUser } = await import("@/lib/user/service");
|
const { getUser } = await import("@/lib/user/service");
|
||||||
const { getProjectEnvironmentsByOrganizationIds } = await import("@/lib/project/service");
|
const { getUserProjectEnvironmentsByOrganizationIds } = await import("@/lib/project/service");
|
||||||
const { getOrganizationsByUserId } = await import("@/lib/organization/service");
|
const { getOrganizationsByUserId } = await import("@/lib/organization/service");
|
||||||
const { getMembershipByUserIdOrganizationId } = await import("@/lib/membership/service");
|
const { getMembershipByUserIdOrganizationId } = await import("@/lib/membership/service");
|
||||||
const { getAccessFlags } = await import("@/lib/membership/utils");
|
const { getAccessFlags } = await import("@/lib/membership/utils");
|
||||||
@@ -310,7 +310,7 @@ describe("Page", () => {
|
|||||||
} as any);
|
} as any);
|
||||||
vi.mocked(getIsFreshInstance).mockResolvedValue(false);
|
vi.mocked(getIsFreshInstance).mockResolvedValue(false);
|
||||||
vi.mocked(getUser).mockResolvedValue(mockUser);
|
vi.mocked(getUser).mockResolvedValue(mockUser);
|
||||||
vi.mocked(getProjectEnvironmentsByOrganizationIds).mockResolvedValue(
|
vi.mocked(getUserProjectEnvironmentsByOrganizationIds).mockResolvedValue(
|
||||||
mockUserProjects as unknown as TProject[]
|
mockUserProjects as unknown as TProject[]
|
||||||
);
|
);
|
||||||
vi.mocked(getOrganizationsByUserId).mockResolvedValue([mockOrganization]);
|
vi.mocked(getOrganizationsByUserId).mockResolvedValue([mockOrganization]);
|
||||||
@@ -334,7 +334,7 @@ describe("Page", () => {
|
|||||||
const { getOrganizationsByUserId } = await import("@/lib/organization/service");
|
const { getOrganizationsByUserId } = await import("@/lib/organization/service");
|
||||||
const { getMembershipByUserIdOrganizationId } = await import("@/lib/membership/service");
|
const { getMembershipByUserIdOrganizationId } = await import("@/lib/membership/service");
|
||||||
const { getAccessFlags } = await import("@/lib/membership/utils");
|
const { getAccessFlags } = await import("@/lib/membership/utils");
|
||||||
const { getProjectEnvironmentsByOrganizationIds } = await import("@/lib/project/service");
|
const { getUserProjectEnvironmentsByOrganizationIds } = await import("@/lib/project/service");
|
||||||
const { render } = await import("@testing-library/react");
|
const { render } = await import("@testing-library/react");
|
||||||
|
|
||||||
const mockUser: TUser = {
|
const mockUser: TUser = {
|
||||||
@@ -432,7 +432,7 @@ describe("Page", () => {
|
|||||||
vi.mocked(getUser).mockResolvedValue(mockUser);
|
vi.mocked(getUser).mockResolvedValue(mockUser);
|
||||||
vi.mocked(getOrganizationsByUserId).mockResolvedValue([mockOrganization]);
|
vi.mocked(getOrganizationsByUserId).mockResolvedValue([mockOrganization]);
|
||||||
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue(mockMembership);
|
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue(mockMembership);
|
||||||
vi.mocked(getProjectEnvironmentsByOrganizationIds).mockResolvedValue(mockUserProjects);
|
vi.mocked(getUserProjectEnvironmentsByOrganizationIds).mockResolvedValue(mockUserProjects);
|
||||||
vi.mocked(getAccessFlags).mockReturnValue({
|
vi.mocked(getAccessFlags).mockReturnValue({
|
||||||
isManager: false,
|
isManager: false,
|
||||||
isOwner: false,
|
isOwner: false,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getIsFreshInstance } from "@/lib/instance/service";
|
|||||||
import { getMembershipByUserIdOrganizationId } from "@/lib/membership/service";
|
import { getMembershipByUserIdOrganizationId } from "@/lib/membership/service";
|
||||||
import { getAccessFlags } from "@/lib/membership/utils";
|
import { getAccessFlags } from "@/lib/membership/utils";
|
||||||
import { getOrganizationsByUserId } from "@/lib/organization/service";
|
import { getOrganizationsByUserId } from "@/lib/organization/service";
|
||||||
import { getProjectEnvironmentsByOrganizationIds } from "@/lib/project/service";
|
import { getUserProjectEnvironmentsByOrganizationIds } from "@/lib/project/service";
|
||||||
import { getUser } from "@/lib/user/service";
|
import { getUser } from "@/lib/user/service";
|
||||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||||
import { ClientLogout } from "@/modules/ui/components/client-logout";
|
import { ClientLogout } from "@/modules/ui/components/client-logout";
|
||||||
@@ -34,7 +34,10 @@ const Page = async () => {
|
|||||||
return redirect("/setup/organization/create");
|
return redirect("/setup/organization/create");
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectsByOrg = await getProjectEnvironmentsByOrganizationIds(userOrganizations.map((org) => org.id));
|
const projectsByOrg = await getUserProjectEnvironmentsByOrganizationIds(
|
||||||
|
userOrganizations.map((org) => org.id),
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
|
||||||
// Flatten all environments from all projects across all organizations
|
// Flatten all environments from all projects across all organizations
|
||||||
const allEnvironments = projectsByOrg.flatMap((project) => project.environments);
|
const allEnvironments = projectsByOrg.flatMap((project) => project.environments);
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { ITEMS_PER_PAGE } from "../constants";
|
|||||||
import {
|
import {
|
||||||
getProject,
|
getProject,
|
||||||
getProjectByEnvironmentId,
|
getProjectByEnvironmentId,
|
||||||
getProjectEnvironmentsByOrganizationIds,
|
|
||||||
getProjects,
|
getProjects,
|
||||||
|
getUserProjectEnvironmentsByOrganizationIds,
|
||||||
getUserProjects,
|
getUserProjects,
|
||||||
} from "./service";
|
} from "./service";
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ vi.mock("@formbricks/database", () => ({
|
|||||||
},
|
},
|
||||||
membership: {
|
membership: {
|
||||||
findFirst: vi.fn(),
|
findFirst: vi.fn(),
|
||||||
|
findMany: vi.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -488,6 +489,7 @@ describe("Project Service", () => {
|
|||||||
test("getProjectsByOrganizationIds should return projects for given organization IDs", async () => {
|
test("getProjectsByOrganizationIds should return projects for given organization IDs", async () => {
|
||||||
const organizationId1 = createId();
|
const organizationId1 = createId();
|
||||||
const organizationId2 = createId();
|
const organizationId2 = createId();
|
||||||
|
const userId = createId();
|
||||||
const mockProjects = [
|
const mockProjects = [
|
||||||
{
|
{
|
||||||
environments: [],
|
environments: [],
|
||||||
@@ -497,16 +499,34 @@ describe("Project Service", () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.membership.findMany).mockResolvedValue([
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
organizationId: organizationId1,
|
||||||
|
role: "owner" as any,
|
||||||
|
accepted: true,
|
||||||
|
deprecatedRole: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
organizationId: organizationId2,
|
||||||
|
role: "owner" as any,
|
||||||
|
accepted: true,
|
||||||
|
deprecatedRole: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
vi.mocked(prisma.project.findMany).mockResolvedValue(mockProjects as any);
|
vi.mocked(prisma.project.findMany).mockResolvedValue(mockProjects as any);
|
||||||
|
|
||||||
const result = await getProjectEnvironmentsByOrganizationIds([organizationId1, organizationId2]);
|
const result = await getUserProjectEnvironmentsByOrganizationIds(
|
||||||
|
[organizationId1, organizationId2],
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
|
||||||
expect(result).toEqual(mockProjects);
|
expect(result).toEqual(mockProjects);
|
||||||
expect(prisma.project.findMany).toHaveBeenCalledWith({
|
expect(prisma.project.findMany).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
organizationId: {
|
OR: [{ organizationId: organizationId1 }, { organizationId: organizationId2 }],
|
||||||
in: [organizationId1, organizationId2],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
select: { environments: true },
|
select: { environments: true },
|
||||||
});
|
});
|
||||||
@@ -515,17 +535,36 @@ describe("Project Service", () => {
|
|||||||
test("getProjectsByOrganizationIds should return empty array when no projects are found", async () => {
|
test("getProjectsByOrganizationIds should return empty array when no projects are found", async () => {
|
||||||
const organizationId1 = createId();
|
const organizationId1 = createId();
|
||||||
const organizationId2 = createId();
|
const organizationId2 = createId();
|
||||||
|
const userId = createId();
|
||||||
|
|
||||||
|
vi.mocked(prisma.membership.findMany).mockResolvedValue([
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
organizationId: organizationId1,
|
||||||
|
role: "owner" as any,
|
||||||
|
accepted: true,
|
||||||
|
deprecatedRole: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
organizationId: organizationId2,
|
||||||
|
role: "owner" as any,
|
||||||
|
accepted: true,
|
||||||
|
deprecatedRole: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
vi.mocked(prisma.project.findMany).mockResolvedValue([]);
|
vi.mocked(prisma.project.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
const result = await getProjectEnvironmentsByOrganizationIds([organizationId1, organizationId2]);
|
const result = await getUserProjectEnvironmentsByOrganizationIds(
|
||||||
|
[organizationId1, organizationId2],
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual([]);
|
||||||
expect(prisma.project.findMany).toHaveBeenCalledWith({
|
expect(prisma.project.findMany).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
organizationId: {
|
OR: [{ organizationId: organizationId1 }, { organizationId: organizationId2 }],
|
||||||
in: [organizationId1, organizationId2],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
select: { environments: true },
|
select: { environments: true },
|
||||||
});
|
});
|
||||||
@@ -534,18 +573,111 @@ describe("Project Service", () => {
|
|||||||
test("getProjectsByOrganizationIds should throw DatabaseError when prisma throws", async () => {
|
test("getProjectsByOrganizationIds should throw DatabaseError when prisma throws", async () => {
|
||||||
const organizationId1 = createId();
|
const organizationId1 = createId();
|
||||||
const organizationId2 = createId();
|
const organizationId2 = createId();
|
||||||
|
const userId = createId();
|
||||||
const prismaError = new Prisma.PrismaClientKnownRequestError("Database error", {
|
const prismaError = new Prisma.PrismaClientKnownRequestError("Database error", {
|
||||||
code: "P2002",
|
code: "P2002",
|
||||||
clientVersion: "5.0.0",
|
clientVersion: "5.0.0",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
vi.mocked(prisma.membership.findMany).mockResolvedValue([
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
organizationId: organizationId1,
|
||||||
|
role: "owner" as any,
|
||||||
|
accepted: true,
|
||||||
|
deprecatedRole: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
vi.mocked(prisma.project.findMany).mockRejectedValue(prismaError);
|
vi.mocked(prisma.project.findMany).mockRejectedValue(prismaError);
|
||||||
|
|
||||||
await expect(getProjectEnvironmentsByOrganizationIds([organizationId1, organizationId2])).rejects.toThrow(
|
await expect(
|
||||||
DatabaseError
|
getUserProjectEnvironmentsByOrganizationIds([organizationId1, organizationId2], userId)
|
||||||
);
|
).rejects.toThrow(DatabaseError);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getProjectsByOrganizationIds should throw ValidationError with wrong input", async () => {
|
test("getProjectsByOrganizationIds should throw ValidationError with wrong input", async () => {
|
||||||
await expect(getProjectEnvironmentsByOrganizationIds(["wrong-id"])).rejects.toThrow(ValidationError);
|
const userId = createId();
|
||||||
|
await expect(getUserProjectEnvironmentsByOrganizationIds(["wrong-id"], userId)).rejects.toThrow(
|
||||||
|
ValidationError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getProjectsByOrganizationIds should return empty array when user has no memberships", async () => {
|
||||||
|
const organizationId1 = createId();
|
||||||
|
const organizationId2 = createId();
|
||||||
|
const userId = createId();
|
||||||
|
|
||||||
|
// Mock no memberships found
|
||||||
|
vi.mocked(prisma.membership.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = await getUserProjectEnvironmentsByOrganizationIds(
|
||||||
|
[organizationId1, organizationId2],
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
expect(prisma.membership.findMany).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
organizationId: {
|
||||||
|
in: [organizationId1, organizationId2],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Should not call project.findMany when no memberships
|
||||||
|
expect(prisma.project.findMany).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getProjectsByOrganizationIds should handle member role with team access", async () => {
|
||||||
|
const organizationId1 = createId();
|
||||||
|
const organizationId2 = createId();
|
||||||
|
const userId = createId();
|
||||||
|
const mockProjects = [
|
||||||
|
{
|
||||||
|
environments: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mock membership where user is a member
|
||||||
|
vi.mocked(prisma.membership.findMany).mockResolvedValue([
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
organizationId: organizationId1,
|
||||||
|
role: "member" as any,
|
||||||
|
accepted: true,
|
||||||
|
deprecatedRole: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
vi.mocked(prisma.project.findMany).mockResolvedValue(mockProjects as any);
|
||||||
|
|
||||||
|
const result = await getUserProjectEnvironmentsByOrganizationIds(
|
||||||
|
[organizationId1, organizationId2],
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockProjects);
|
||||||
|
expect(prisma.project.findMany).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
organizationId: organizationId1,
|
||||||
|
projectTeams: {
|
||||||
|
some: {
|
||||||
|
team: {
|
||||||
|
teamUsers: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
select: { environments: true },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -171,20 +171,56 @@ export const getOrganizationProjectsCount = reactCache(async (organizationId: st
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getProjectEnvironmentsByOrganizationIds = reactCache(
|
export const getUserProjectEnvironmentsByOrganizationIds = reactCache(
|
||||||
async (organizationIds: string[]): Promise<Pick<TProject, "environments">[]> => {
|
async (organizationIds: string[], userId: string): Promise<Pick<TProject, "environments">[]> => {
|
||||||
validateInputs([organizationIds, ZId.array()]);
|
validateInputs([organizationIds, ZId.array()], [userId, ZId]);
|
||||||
try {
|
try {
|
||||||
if (organizationIds.length === 0) {
|
if (organizationIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const projects = await prisma.project.findMany({
|
const memberships = await prisma.membership.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
userId,
|
||||||
organizationId: {
|
organizationId: {
|
||||||
in: organizationIds,
|
in: organizationIds,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (memberships.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const whereConditions: Prisma.ProjectWhereInput[] = memberships.map((membership) => {
|
||||||
|
let projectWhereClause: Prisma.ProjectWhereInput = {
|
||||||
|
organizationId: membership.organizationId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (membership.role === "member") {
|
||||||
|
projectWhereClause = {
|
||||||
|
...projectWhereClause,
|
||||||
|
projectTeams: {
|
||||||
|
some: {
|
||||||
|
team: {
|
||||||
|
teamUsers: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectWhereClause;
|
||||||
|
});
|
||||||
|
|
||||||
|
const projects = await prisma.project.findMany({
|
||||||
|
where: {
|
||||||
|
OR: whereConditions,
|
||||||
|
},
|
||||||
select: { environments: true },
|
select: { environments: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user