Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { Team, User, Membership } from "@prisma/client";
import { Prisma } from "@prisma/client";
import { describe, expect, it } from "vitest";

import prisma from "@calcom/prisma";
import { BookingStatus, MembershipRole } from "@calcom/prisma/enums";

import { InsightsBookingService } from "../../service/insightsBooking";

const NOTHING_CONDITION = Prisma.sql`1=0`;

// Helper function to create unique test data
async function createTestData({
teamRole = MembershipRole.MEMBER,
Expand Down Expand Up @@ -204,7 +207,7 @@ describe("InsightsBookingService Integration Tests", () => {
});

const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual({ id: -1 });
expect(conditions).toEqual(NOTHING_CONDITION);
});

it("should return NOTHING for non-owner/admin user", async () => {
Expand Down Expand Up @@ -239,7 +242,7 @@ describe("InsightsBookingService Integration Tests", () => {
});

const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual({ id: -1 });
expect(conditions).toEqual(NOTHING_CONDITION);

// Clean up
await prisma.membership.delete({
Expand Down Expand Up @@ -267,14 +270,7 @@ describe("InsightsBookingService Integration Tests", () => {
});

const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual({
AND: [
{
userId: testData.user.id,
teamId: null,
},
],
});
expect(conditions).toEqual(Prisma.sql`("userId" = ${testData.user.id}) AND ("teamId" IS NULL)`);

await testData.cleanup();
});
Expand All @@ -296,24 +292,11 @@ describe("InsightsBookingService Integration Tests", () => {
});

const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual({
AND: [
{
OR: [
{
teamId: testData.team.id,
isTeamBooking: true,
},
{
userId: {
in: [testData.user.id],
},
isTeamBooking: false,
},
],
},
],
});
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();
Expand Down Expand Up @@ -346,26 +329,18 @@ describe("InsightsBookingService Integration Tests", () => {

const conditions = await service.getAuthorizationConditions();

expect(conditions).toEqual({
AND: [
{
OR: [
{
teamId: {
in: [testData.org.id, testData.team.id, team2.id, team3.id],
},
isTeamBooking: true,
},
{
userId: {
in: [testData.user.id, user2.id, user3.id],
},
isTeamBooking: false,
},
],
},
],
});
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))`
);
Comment on lines +332 to +343

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the test file
fd -n "insightsBooking.integration-test.ts" -type f

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 317


🏁 Script executed:

# Look at the specific test section around lines 332-343
cat -n packages/lib/server/service/__tests__/insightsBooking.integration-test.ts | sed -n '320,355p'

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 1263


🏁 Script executed:

# Search for findAllByParentId and findAllByTeamIds implementations
rg -n "findAllByParentId|findAllByTeamIds" --type ts -B 2 -A 10

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 7403


🏁 Script executed:

# Check for any ORDER BY clauses in the service implementation
rg -n "ORDER BY|orderBy" packages/lib/server/service --type ts -B 2 -A 2

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 69


🏁 Script executed:

# Get the complete findAllByTeamIds implementation
cat -n packages/lib/server/repository/membership.ts | sed -n '402,420p'

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 567


🏁 Script executed:

# Check the complete findAllByParentId implementation
cat -n packages/lib/server/repository/team.ts | sed -n '185,210p'

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 751


🏁 Script executed:

# Look at the test setup to see how test data is created
cat -n packages/lib/server/service/__tests__/insightsBooking.integration-test.ts | sed -n '1,100p'

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 3551


🏁 Script executed:

# Search for where team2 and team3 are created in the test
rg -n "team2\|team3" packages/lib/server/service/__tests__/insightsBooking.integration-test.ts -B 3 -A 3

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 69


🏁 Script executed:

# Look for team2 and team3 creation in the test file
cat -n packages/lib/server/service/__tests__/insightsBooking.integration-test.ts | sed -n '100,250p'

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 5623


🏁 Script executed:

# Search for where org scope test is set up
rg -n "scope.*org" packages/lib/server/service/__tests__/insightsBooking.integration-test.ts -B 20 | head -80

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 1778


🏁 Script executed:

# Let me check how the service actually constructs and returns the conditions
cat -n packages/lib/server/service/insightsBooking.ts | sed -n '140,175p'

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 1575


🏁 Script executed:

# Also check if there's any sorting applied to uniqueUserIds
cat -n packages/lib/server/service/insightsBooking.ts | sed -n '160,170p'

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 539


🏁 Script executed:

# Check the actual usage of findAllByParentId - does the full select matter?
cat -n packages/lib/server/repository/team.ts | sed -n '1,30p'

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 1187


🏁 Script executed:

# Check if there's any implicit ordering by looking at database schema or Prisma schema
cat -n packages/lib/server/repository/team.ts | sed -n '180,220p'

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 1049


🏁 Script executed:

# Look for the teamSelect default to see if it includes any ordering
rg -n "const teamSelect" packages/lib/server/repository/team.ts -A 15

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 497


Test may be flaky due to unordered array comparisons in SQL assertions.

The test assumes teamIds and userIds arrays will be in a specific order. However, both findAllByParentId (team.ts:185-197) and findAllByTeamIds (membership.ts:402-419) use findMany without an orderBy clause, so database query results have no guaranteed order. Additionally, line 167 converts userIdsFromOrg to a Set and back to an array, which doesn't guarantee the order matches the test's hardcoded expectation.

If database query execution order changes (due to indexes, query planner optimization, or different database systems), the test will fail despite semantically correct SQL.

Options:

  • Sort arrays before building the SQL: const teamIds = [options.orgId, ...teamsFromOrg.map((t) => t.id).sort((a, b) => a - b)];
  • Use a custom matcher that compares arrays ignoring order
  • Add explicit orderBy to the repository methods to ensure deterministic results
🤖 Prompt for AI Agents
In @packages/lib/server/service/__tests__/insightsBooking.integration-test.ts
around lines 332 - 343, The assertion is flaky because the arrays inserted into
the Prisma.sql template can be in any order; fix by making the arrays
deterministic before building the SQL: sort the teamIds and userIds arrays used
to construct the Prisma.sql (the arrays currently produced from teamsFromOrg /
userIdsFromOrg and used where `expect(conditions).toEqual(Prisma.sql\`...\`)` is
asserted), e.g. convert the sets/arrays to arrays then call .sort() (numeric or
lexicographic consistent with your id types) so the generated SQL always has a
stable ordering; alternatively add explicit orderBy in the repository methods
`findAllByParentId` and `findAllByTeamIds` to guarantee deterministic query
results—pick one and apply the change where the test builds the `teamIds` and
`userIds` used in the Prisma.sql template.


await testData.cleanup();
});
Expand Down Expand Up @@ -406,13 +381,9 @@ describe("InsightsBookingService Integration Tests", () => {
});

const conditions = await service.getFilterConditions();
expect(conditions).toEqual({
AND: [
{
OR: [{ eventTypeId: testData.eventType.id }, { eventParentId: testData.eventType.id }],
},
],
});
expect(conditions).toEqual(
Prisma.sql`("eventTypeId" = ${testData.eventType.id}) OR ("eventParentId" = ${testData.eventType.id})`
);

await testData.cleanup();
});
Expand All @@ -433,13 +404,7 @@ describe("InsightsBookingService Integration Tests", () => {
});

const conditions = await service.getFilterConditions();
expect(conditions).toEqual({
AND: [
{
userId: testData.user.id,
},
],
});
expect(conditions).toEqual(Prisma.sql`"userId" = ${testData.user.id}`);

await testData.cleanup();
});
Expand All @@ -461,90 +426,15 @@ describe("InsightsBookingService Integration Tests", () => {
});

const conditions = await service.getFilterConditions();
expect(conditions).toEqual({
AND: [
{
OR: [{ eventTypeId: testData.eventType.id }, { eventParentId: testData.eventType.id }],
},
{
userId: testData.user.id,
},
],
});

await testData.cleanup();
});
});

describe("Caching", () => {
it("should cache authorization 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,
},
});

// First call should build conditions
const conditions1 = await service.getAuthorizationConditions();
expect(conditions1).toEqual({
AND: [
{
userId: testData.user.id,
teamId: null,
},
],
});

// Second call should use cached conditions
const conditions2 = await service.getAuthorizationConditions();
expect(conditions2).toEqual(conditions1);

// Clean up
await testData.cleanup();
});

it("should cache filter conditions", async () => {
const testData = await createTestData();

const service = new InsightsBookingService({
prisma,
options: {
scope: "user",
userId: testData.user.id,
orgId: testData.org.id,
},
filters: {
eventTypeId: testData.eventType.id,
},
});

// First call should build conditions
const conditions1 = await service.getFilterConditions();
expect(conditions1).toEqual({
AND: [
{
OR: [{ eventTypeId: testData.eventType.id }, { eventParentId: testData.eventType.id }],
},
],
});

// Second call should use cached conditions
const conditions2 = await service.getFilterConditions();
expect(conditions2).toEqual(conditions1);
expect(conditions).toEqual(
Prisma.sql`(("eventTypeId" = ${testData.eventType.id}) OR ("eventParentId" = ${testData.eventType.id})) AND ("userId" = ${testData.user.id})`
);

await testData.cleanup();
});
});

describe("findMany", () => {
describe("getBaseConditions", () => {
it("should combine authorization and filter conditions", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
Expand Down Expand Up @@ -587,16 +477,18 @@ describe("InsightsBookingService Integration Tests", () => {
},
});

const results = await service.findMany({
select: {
id: true,
title: true,
},
});
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).toHaveLength(1);
expect(results[0]?.id).toBe(userBooking.id);
expect(results).toEqual([
{
id: userBooking.id,
},
]);

// Clean up
await prisma.booking.delete({
Expand Down
Loading