* feat: make orgId optional for user and team scopes in InsightsRoutingBaseService - User scope authorization only checks formUserId and formTeamId IS NULL - Team scope now supports standalone teams without organizations - Add validation logic to return NOTHING_CONDITION if team belongs to org but no orgId provided - Add comprehensive test coverage for null/undefined orgId scenarios in both scopes - Aligns schema with actual usage patterns and supports teams without organizations Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * feat: extend orgId optional support to InsightsBookingBaseService - Make orgId optional for user and team scopes in InsightsBookingBaseService - Update InsightsBookingServicePublicOptions type to allow orgId: number | null - Add validation logic for team scope to handle missing orgId - Add comprehensive test coverage for null/undefined orgId scenarios - Fix type casting issues in test file - Maintains backward compatibility while supporting teams without organizations Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: correct authorization logic for optional orgId in team scope - Skip isOwnerOrAdmin check for team scope when orgId is null (standalone teams) - Maintain security for org scope and team scope with orgId - Fixes integration test failures for null orgId test cases Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: use != null instead of !== undefined for orgId checks - Properly handle both null and undefined orgId values in authorization logic - Fix integration test failures where null orgId was incorrectly triggering isOwnerOrAdmin check - Ensure team scope with null orgId skips ownership validation for standalone teams Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: always validate team membership for team scope - Remove orgId condition from isOwnerOrAdmin check for team scope - Ensure both standalone teams and org-based teams require ownership validation - Maintain orgId validation logic in buildTeamAuthorizationCondition methods Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: use nullish() for orgId schema validation - Change from .optional() to .nullish() to allow both null and undefined - Fixes schema validation when tests pass orgId: null - Resolves authorization logic returning NOTHING_CONDITION for valid cases Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * feat: replace isOwnerOrAdmin with PBAC checkPermission in insights services - Replace isOwnerOrAdmin method in InsightsBookingBaseService with checkPermission from PermissionCheckService - Replace isOwnerOrAdmin method in InsightsRoutingBaseService with checkPermission from PermissionCheckService - Use permission 'insights.read' with fallback roles MembershipRole.OWNER and MembershipRole.ADMIN - Maintain same method signature and behavior while leveraging PBAC system Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * clean up types * add comment --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
750 lines
21 KiB
TypeScript
750 lines
21 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { ColumnFilterType } from "@calcom/features/data-table/lib/types";
|
|
import prisma from "@calcom/prisma";
|
|
import type { Team, User, Membership } from "@calcom/prisma/client";
|
|
import { Prisma } from "@calcom/prisma/client";
|
|
import { BookingStatus, MembershipRole } from "@calcom/prisma/enums";
|
|
|
|
import {
|
|
InsightsBookingBaseService as InsightsBookingService,
|
|
type InsightsBookingServicePublicOptions,
|
|
} from "../InsightsBookingBaseService";
|
|
|
|
const NOTHING_CONDITION = Prisma.sql`1=0`;
|
|
|
|
// Helper function to create unique test data
|
|
async function createTestData({
|
|
teamRole = MembershipRole.MEMBER,
|
|
orgRole = MembershipRole.MEMBER,
|
|
moreTeamsAndUsers = [],
|
|
}: {
|
|
teamRole?: MembershipRole;
|
|
orgRole?: MembershipRole;
|
|
moreTeamsAndUsers?: MembershipRole[][]; // Array of teams, each team has array of user roles
|
|
} = {}) {
|
|
const timestamp = Date.now();
|
|
const randomSuffix = Math.random().toString(36).substring(7);
|
|
|
|
// Create test user
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
email: `test-user-${timestamp}-${randomSuffix}@example.com`,
|
|
username: `testuser-${timestamp}-${randomSuffix}`,
|
|
name: "Test User",
|
|
},
|
|
});
|
|
|
|
// Create test organization
|
|
const org = await prisma.team.create({
|
|
data: {
|
|
name: `Test Org ${timestamp}-${randomSuffix}`,
|
|
slug: `test-org-${timestamp}-${randomSuffix}`,
|
|
isOrganization: true,
|
|
},
|
|
});
|
|
|
|
// Create test team under the organization
|
|
const team = await prisma.team.create({
|
|
data: {
|
|
name: `Test Team ${timestamp}-${randomSuffix}`,
|
|
slug: `test-team-${timestamp}-${randomSuffix}`,
|
|
isOrganization: false,
|
|
parentId: org.id,
|
|
},
|
|
});
|
|
|
|
// Create test event type
|
|
const eventType = await prisma.eventType.create({
|
|
data: {
|
|
title: "Test Event Type",
|
|
slug: "test-event-type",
|
|
length: 60,
|
|
userId: user.id,
|
|
teamId: team.id,
|
|
},
|
|
});
|
|
|
|
// Create test booking
|
|
const booking = await prisma.booking.create({
|
|
data: {
|
|
uid: `test-booking-${timestamp}-${randomSuffix}`,
|
|
title: "Test Booking",
|
|
startTime: new Date(),
|
|
endTime: new Date(Date.now() + 60 * 60 * 1000),
|
|
userId: user.id,
|
|
eventTypeId: eventType.id,
|
|
status: BookingStatus.PENDING,
|
|
},
|
|
});
|
|
|
|
const membership = await prisma.membership.create({
|
|
data: {
|
|
userId: user.id,
|
|
teamId: team.id,
|
|
role: teamRole,
|
|
accepted: true,
|
|
},
|
|
});
|
|
|
|
const orgMembership = await prisma.membership.create({
|
|
data: {
|
|
userId: user.id,
|
|
teamId: org.id,
|
|
role: orgRole,
|
|
accepted: true,
|
|
},
|
|
});
|
|
|
|
// Create additional teams and users based on moreTeamsAndUsers
|
|
const additionalTeams: Team[] = [];
|
|
const additionalUsers: User[] = [];
|
|
const additionalMemberships: Membership[] = [];
|
|
|
|
for (let teamIndex = 0; teamIndex < moreTeamsAndUsers.length; teamIndex++) {
|
|
const teamUserRoles = moreTeamsAndUsers[teamIndex];
|
|
|
|
// Create the team
|
|
const additionalTeam = await prisma.team.create({
|
|
data: {
|
|
name: `Test Team ${teamIndex + 2} ${timestamp}-${randomSuffix}`,
|
|
slug: `test-team-${teamIndex + 2}-${timestamp}-${randomSuffix}`,
|
|
isOrganization: false,
|
|
parentId: org.id,
|
|
},
|
|
});
|
|
additionalTeams.push(additionalTeam);
|
|
|
|
// Create users for this team
|
|
for (let userIndex = 0; userIndex < teamUserRoles.length; userIndex++) {
|
|
const userRole = teamUserRoles[userIndex];
|
|
|
|
// Create the user
|
|
const additionalUser = await prisma.user.create({
|
|
data: {
|
|
email: `test-user-team${teamIndex + 2}-${userIndex + 1}-${timestamp}-${randomSuffix}@example.com`,
|
|
username: `testuser-team${teamIndex + 2}-${userIndex + 1}-${timestamp}-${randomSuffix}`,
|
|
name: `Test User Team${teamIndex + 2}-${userIndex + 1}`,
|
|
},
|
|
});
|
|
additionalUsers.push(additionalUser);
|
|
|
|
// Create the membership
|
|
const additionalMembership = await prisma.membership.create({
|
|
data: {
|
|
userId: additionalUser.id,
|
|
teamId: additionalTeam.id,
|
|
role: userRole,
|
|
accepted: true,
|
|
},
|
|
});
|
|
additionalMemberships.push(additionalMembership);
|
|
}
|
|
}
|
|
|
|
// Return test data and cleanup function
|
|
return {
|
|
user,
|
|
org,
|
|
orgMembership,
|
|
team,
|
|
eventType,
|
|
booking,
|
|
membership,
|
|
additionalTeams,
|
|
additionalUsers,
|
|
additionalMemberships,
|
|
cleanup: async () => {
|
|
// Clean up additional memberships
|
|
for (const membership of additionalMemberships) {
|
|
await prisma.membership.delete({
|
|
where: { id: membership.id },
|
|
});
|
|
}
|
|
|
|
// Clean up additional users
|
|
for (const user of additionalUsers) {
|
|
await prisma.user.delete({
|
|
where: { id: user.id },
|
|
});
|
|
}
|
|
|
|
// Clean up additional teams
|
|
for (const team of additionalTeams) {
|
|
await prisma.team.delete({
|
|
where: { id: team.id },
|
|
});
|
|
}
|
|
|
|
// Clean up main test data
|
|
await prisma.booking.deleteMany({
|
|
where: { id: booking.id },
|
|
});
|
|
await prisma.eventType.deleteMany({
|
|
where: { id: eventType.id },
|
|
});
|
|
await prisma.membership.deleteMany({
|
|
where: { id: membership.id },
|
|
});
|
|
await prisma.membership.delete({
|
|
where: { id: orgMembership.id },
|
|
});
|
|
await prisma.team.deleteMany({
|
|
where: { id: team.id },
|
|
});
|
|
await prisma.team.deleteMany({
|
|
where: { id: org.id },
|
|
});
|
|
await prisma.user.deleteMany({
|
|
where: { id: user.id },
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("InsightsBookingService Integration Tests", () => {
|
|
describe("Authorization Conditions", () => {
|
|
it("should return NOTHING for invalid options", async () => {
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: null as unknown as InsightsBookingServicePublicOptions,
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
expect(conditions).toEqual(NOTHING_CONDITION);
|
|
});
|
|
|
|
it("should return NOTHING for non-owner/admin user", async () => {
|
|
const testData = await createTestData();
|
|
|
|
// Create a regular user who is not an owner/admin
|
|
const regularUser = await prisma.user.create({
|
|
data: {
|
|
email: `regular-user-${Date.now()}-${Math.random().toString(36).substring(7)}@example.com`,
|
|
username: `regularuser-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
|
name: "Regular User",
|
|
},
|
|
});
|
|
|
|
// Add the user as a regular member (not owner/admin) to the organization
|
|
const membership = await prisma.membership.create({
|
|
data: {
|
|
userId: regularUser.id,
|
|
teamId: testData.org.id,
|
|
role: MembershipRole.MEMBER,
|
|
accepted: true,
|
|
},
|
|
});
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "org",
|
|
userId: regularUser.id,
|
|
orgId: testData.org.id,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
expect(conditions).toEqual(NOTHING_CONDITION);
|
|
|
|
// Clean up
|
|
await prisma.membership.delete({
|
|
where: { id: membership.id },
|
|
});
|
|
await prisma.user.delete({
|
|
where: { id: regularUser.id },
|
|
});
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build user scope conditions", async () => {
|
|
const testData = await createTestData({
|
|
teamRole: MembershipRole.OWNER,
|
|
orgRole: MembershipRole.OWNER,
|
|
});
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "user",
|
|
userId: testData.user.id,
|
|
orgId: testData.org.id,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
expect(conditions).toEqual(Prisma.sql`("userId" = ${testData.user.id}) AND ("teamId" IS NULL)`);
|
|
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build team scope conditions", async () => {
|
|
const testData = await createTestData({
|
|
teamRole: MembershipRole.OWNER,
|
|
orgRole: MembershipRole.OWNER,
|
|
});
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "team",
|
|
userId: testData.user.id,
|
|
orgId: testData.org.id,
|
|
teamId: testData.team.id,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
expect(conditions).toEqual(
|
|
Prisma.sql`(("teamId" = ${testData.team.id}) AND ("isTeamBooking" = true)) OR (("userId" = ANY(${[
|
|
testData.user.id,
|
|
]})) AND ("isTeamBooking" = false))`
|
|
);
|
|
|
|
// Clean up
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build org scope conditions", async () => {
|
|
const testData = await createTestData({
|
|
teamRole: MembershipRole.OWNER,
|
|
orgRole: MembershipRole.OWNER,
|
|
moreTeamsAndUsers: [
|
|
[MembershipRole.MEMBER], // Team 2: 1 user with MEMBER role
|
|
[MembershipRole.MEMBER], // Team 3: 1 user with MEMBER role
|
|
],
|
|
});
|
|
|
|
// Get references to additional data
|
|
const user2 = testData.additionalUsers[0]; // First user from first team
|
|
const user3 = testData.additionalUsers[1]; // First user from second team
|
|
const team2 = testData.additionalTeams[0]; // First additional team
|
|
const team3 = testData.additionalTeams[1]; // Second additional team
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "org",
|
|
userId: testData.user.id,
|
|
orgId: testData.org.id,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
|
|
expect(conditions).toEqual(
|
|
Prisma.sql`(("teamId" = ANY(${[
|
|
testData.org.id,
|
|
testData.team.id,
|
|
team2.id,
|
|
team3.id,
|
|
]})) AND ("isTeamBooking" = true)) OR (("userId" = ANY(${[
|
|
testData.user.id,
|
|
user2.id,
|
|
user3.id,
|
|
]})) AND ("isTeamBooking" = false))`
|
|
);
|
|
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build user scope conditions with null orgId", async () => {
|
|
const testData = await createTestData({
|
|
teamRole: MembershipRole.OWNER,
|
|
orgRole: MembershipRole.OWNER,
|
|
});
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "user",
|
|
userId: testData.user.id,
|
|
orgId: null,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
expect(conditions).toEqual(Prisma.sql`("userId" = ${testData.user.id}) AND ("teamId" IS NULL)`);
|
|
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build user scope conditions with undefined orgId", async () => {
|
|
const testData = await createTestData({
|
|
teamRole: MembershipRole.OWNER,
|
|
orgRole: MembershipRole.OWNER,
|
|
});
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "user",
|
|
userId: testData.user.id,
|
|
orgId: null,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
expect(conditions).toEqual(Prisma.sql`("userId" = ${testData.user.id}) AND ("teamId" IS NULL)`);
|
|
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build team scope conditions with null orgId for standalone team", async () => {
|
|
const testData = await createTestData({
|
|
teamRole: MembershipRole.OWNER,
|
|
orgRole: MembershipRole.OWNER,
|
|
});
|
|
|
|
const standaloneTeam = await prisma.team.create({
|
|
data: {
|
|
name: "Standalone Team",
|
|
slug: `standalone-team-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
|
isOrganization: false,
|
|
parentId: null,
|
|
},
|
|
});
|
|
|
|
await prisma.membership.create({
|
|
data: {
|
|
userId: testData.user.id,
|
|
teamId: standaloneTeam.id,
|
|
role: MembershipRole.OWNER,
|
|
accepted: true,
|
|
},
|
|
});
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "team",
|
|
userId: testData.user.id,
|
|
orgId: null,
|
|
teamId: standaloneTeam.id,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
expect(conditions).toEqual(
|
|
Prisma.sql`(("teamId" = ${standaloneTeam.id}) AND ("isTeamBooking" = true)) OR (("userId" = ANY(${[
|
|
testData.user.id,
|
|
]})) AND ("isTeamBooking" = false))`
|
|
);
|
|
|
|
await prisma.membership.deleteMany({
|
|
where: { teamId: standaloneTeam.id },
|
|
});
|
|
await prisma.team.delete({
|
|
where: { id: standaloneTeam.id },
|
|
});
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should return NOTHING_CONDITION for team scope when team belongs to org but no orgId provided", async () => {
|
|
const testData = await createTestData({
|
|
teamRole: MembershipRole.OWNER,
|
|
orgRole: MembershipRole.OWNER,
|
|
});
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "team",
|
|
userId: testData.user.id,
|
|
orgId: null,
|
|
teamId: testData.team.id,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
expect(conditions).toEqual(NOTHING_CONDITION);
|
|
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build team scope conditions with undefined orgId for standalone team", async () => {
|
|
const testData = await createTestData({
|
|
teamRole: MembershipRole.OWNER,
|
|
orgRole: MembershipRole.OWNER,
|
|
});
|
|
|
|
const standaloneTeam = await prisma.team.create({
|
|
data: {
|
|
name: "Standalone Team 2",
|
|
slug: `standalone-team-2-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
|
isOrganization: false,
|
|
parentId: null,
|
|
},
|
|
});
|
|
|
|
await prisma.membership.create({
|
|
data: {
|
|
userId: testData.user.id,
|
|
teamId: standaloneTeam.id,
|
|
role: MembershipRole.OWNER,
|
|
accepted: true,
|
|
},
|
|
});
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "team",
|
|
userId: testData.user.id,
|
|
orgId: null,
|
|
teamId: standaloneTeam.id,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getAuthorizationConditions();
|
|
expect(conditions).toEqual(
|
|
Prisma.sql`(("teamId" = ${standaloneTeam.id}) AND ("isTeamBooking" = true)) OR (("userId" = ANY(${[
|
|
testData.user.id,
|
|
]})) AND ("isTeamBooking" = false))`
|
|
);
|
|
|
|
await prisma.membership.deleteMany({
|
|
where: { teamId: standaloneTeam.id },
|
|
});
|
|
await prisma.team.delete({
|
|
where: { id: standaloneTeam.id },
|
|
});
|
|
await testData.cleanup();
|
|
});
|
|
});
|
|
|
|
describe("Filter Conditions", () => {
|
|
it("should return null when no filters are provided", async () => {
|
|
const testData = await createTestData();
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "user",
|
|
userId: testData.user.id,
|
|
orgId: testData.org.id,
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getFilterConditions();
|
|
expect(conditions).toBeNull();
|
|
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build eventTypeId filter conditions", async () => {
|
|
const testData = await createTestData();
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "user",
|
|
userId: testData.user.id,
|
|
orgId: testData.org.id,
|
|
},
|
|
filters: {
|
|
columnFilters: [
|
|
{
|
|
id: "eventTypeId",
|
|
value: {
|
|
type: ColumnFilterType.MULTI_SELECT,
|
|
data: [testData.eventType.id],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getFilterConditions();
|
|
expect(conditions).toEqual(
|
|
Prisma.sql`("eventTypeId" IN (${Prisma.join([
|
|
testData.eventType.id,
|
|
])}) OR "eventParentId" IN (${Prisma.join([testData.eventType.id])}))`
|
|
);
|
|
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build userId filter conditions", async () => {
|
|
const testData = await createTestData();
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "user",
|
|
userId: testData.user.id,
|
|
orgId: testData.org.id,
|
|
},
|
|
filters: {
|
|
columnFilters: [
|
|
{
|
|
id: "userId",
|
|
value: {
|
|
type: ColumnFilterType.SINGLE_SELECT,
|
|
data: testData.user.id,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getFilterConditions();
|
|
expect(conditions).toEqual(Prisma.sql`"userId" = ${testData.user.id}`);
|
|
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should combine multiple filter conditions", async () => {
|
|
const testData = await createTestData();
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "user",
|
|
userId: testData.user.id,
|
|
orgId: testData.org.id,
|
|
},
|
|
filters: {
|
|
columnFilters: [
|
|
{
|
|
id: "eventTypeId",
|
|
value: {
|
|
type: ColumnFilterType.MULTI_SELECT,
|
|
data: [testData.eventType.id],
|
|
},
|
|
},
|
|
{
|
|
id: "userId",
|
|
value: {
|
|
type: ColumnFilterType.SINGLE_SELECT,
|
|
data: testData.user.id,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getFilterConditions();
|
|
expect(conditions).toEqual(
|
|
Prisma.sql`(("eventTypeId" IN (${Prisma.join([
|
|
testData.eventType.id,
|
|
])}) OR "eventParentId" IN (${Prisma.join([testData.eventType.id])}))) AND ("userId" = ${
|
|
testData.user.id
|
|
})`
|
|
);
|
|
|
|
await testData.cleanup();
|
|
});
|
|
|
|
it("should build status filter conditions", async () => {
|
|
const testData = await createTestData();
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "user",
|
|
userId: testData.user.id,
|
|
orgId: testData.org.id,
|
|
},
|
|
filters: {
|
|
columnFilters: [
|
|
{
|
|
id: "status",
|
|
value: {
|
|
type: ColumnFilterType.MULTI_SELECT,
|
|
data: ["pending", "accepted"],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const conditions = await service.getFilterConditions();
|
|
expect(conditions).toEqual(
|
|
Prisma.sql`"status" IN (${Prisma.join([
|
|
Prisma.sql`${"pending"}::"BookingStatus"`,
|
|
Prisma.sql`${"accepted"}::"BookingStatus"`,
|
|
])})`
|
|
);
|
|
|
|
await testData.cleanup();
|
|
});
|
|
});
|
|
|
|
describe("getBaseConditions", () => {
|
|
it("should combine authorization and filter conditions", async () => {
|
|
const testData = await createTestData({
|
|
teamRole: MembershipRole.OWNER,
|
|
orgRole: MembershipRole.OWNER,
|
|
});
|
|
|
|
// Create a user-scoped event type (not team-scoped)
|
|
const userEventType = await prisma.eventType.create({
|
|
data: {
|
|
title: "User Event Type",
|
|
slug: "user-event-type",
|
|
length: 60,
|
|
userId: testData.user.id,
|
|
teamId: null, // User-scoped, not team-scoped
|
|
},
|
|
});
|
|
|
|
// Create a booking for the user-scoped event type
|
|
const userBooking = await prisma.booking.create({
|
|
data: {
|
|
uid: `user-booking-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
|
title: "User Booking",
|
|
startTime: new Date(),
|
|
endTime: new Date(Date.now() + 60 * 60 * 1000),
|
|
userId: testData.user.id,
|
|
eventTypeId: userEventType.id,
|
|
status: BookingStatus.PENDING,
|
|
},
|
|
});
|
|
|
|
const service = new InsightsBookingService({
|
|
prisma,
|
|
options: {
|
|
scope: "user",
|
|
userId: testData.user.id,
|
|
orgId: testData.org.id,
|
|
},
|
|
filters: {
|
|
columnFilters: [
|
|
{
|
|
id: "eventTypeId",
|
|
value: {
|
|
type: ColumnFilterType.MULTI_SELECT,
|
|
data: [userEventType.id],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const baseConditions = await service.getBaseConditions();
|
|
const results = await prisma.$queryRaw<{ id: number }[]>`
|
|
SELECT id FROM "BookingTimeStatusDenormalized"
|
|
WHERE ${baseConditions}
|
|
`;
|
|
|
|
// Should return the user booking since it matches both conditions
|
|
expect(results).toEqual([
|
|
{
|
|
id: userBooking.id,
|
|
},
|
|
]);
|
|
|
|
// Clean up
|
|
await prisma.booking.delete({
|
|
where: { id: userBooking.id },
|
|
});
|
|
await prisma.eventType.delete({
|
|
where: { id: userEventType.id },
|
|
});
|
|
await testData.cleanup();
|
|
});
|
|
});
|
|
});
|