checks and permission

This commit is contained in:
dal 2025-09-16 15:10:38 -06:00
parent d0659723f2
commit d0c7efc441
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
4 changed files with 512 additions and 15 deletions

View File

@ -8,6 +8,7 @@ import {
checkMetricChatAccess,
checkMetricCollectionAccess,
checkMetricDashboardAccess,
checkMetricReportAccess,
} from './cascading-permissions';
// Mock database queries
@ -15,6 +16,7 @@ vi.mock('@buster/database', () => ({
checkCollectionsContainingAsset: vi.fn(),
checkDashboardsContainingMetric: vi.fn(),
checkChatsContainingAsset: vi.fn(),
checkReportsContainingMetric: vi.fn(),
}));
// Mock hasAssetPermission
@ -32,11 +34,13 @@ describe('Cascading Permissions', () => {
let mockCheckDashboardsContainingMetric: any;
let mockCheckChatsContainingAsset: any;
let mockCheckCollectionsContainingAsset: any;
let mockCheckReportsContainingMetric: any;
let mockHasAssetPermission: any;
let mockGetCachedCascadingPermission: any;
let mockSetCachedCascadingPermission: any;
const mockUser = { id: 'user123', email: 'test@example.com' };
const mockOrganizationId = 'org123';
beforeEach(async () => {
vi.clearAllMocks();
@ -45,6 +49,7 @@ describe('Cascading Permissions', () => {
mockCheckDashboardsContainingMetric = vi.mocked(db.checkDashboardsContainingMetric);
mockCheckChatsContainingAsset = vi.mocked(db.checkChatsContainingAsset);
mockCheckCollectionsContainingAsset = vi.mocked(db.checkCollectionsContainingAsset);
mockCheckReportsContainingMetric = vi.mocked(db.checkReportsContainingMetric);
const permissions = await import('./permissions');
mockHasAssetPermission = vi.mocked(permissions.hasAssetPermission);
@ -56,7 +61,10 @@ describe('Cascading Permissions', () => {
describe('checkMetricDashboardAccess', () => {
it('should return true if user has access to any dashboard containing the metric', async () => {
mockCheckDashboardsContainingMetric.mockResolvedValue([{ id: 'dash1' }, { id: 'dash2' }]);
mockCheckDashboardsContainingMetric.mockResolvedValue([
{ id: 'dash1', organizationId: mockOrganizationId, workspaceSharing: 'none' },
{ id: 'dash2', organizationId: mockOrganizationId, workspaceSharing: 'can_view' },
]);
mockHasAssetPermission
.mockResolvedValueOnce(false) // dash1 - no access
@ -67,11 +75,61 @@ describe('Cascading Permissions', () => {
expect(result).toBe(true);
expect(mockCheckDashboardsContainingMetric).toHaveBeenCalledWith('metric123');
expect(mockHasAssetPermission).toHaveBeenCalledTimes(2);
expect(mockHasAssetPermission).toHaveBeenCalledWith({
expect(mockHasAssetPermission).toHaveBeenNthCalledWith(1, {
assetId: 'dash1',
assetType: 'dashboard',
assetType: 'dashboard_file',
userId: 'user123',
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: 'none',
});
expect(mockHasAssetPermission).toHaveBeenNthCalledWith(2, {
assetId: 'dash2',
assetType: 'dashboard_file',
userId: 'user123',
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: 'can_view',
});
});
it('should handle workspace sharing access', async () => {
mockCheckDashboardsContainingMetric.mockResolvedValue([
{ id: 'dash1', organizationId: mockOrganizationId, workspaceSharing: 'can_edit' },
]);
mockHasAssetPermission.mockResolvedValue(true); // Has access via workspace sharing
const result = await checkMetricDashboardAccess('metric123', mockUser as any);
expect(result).toBe(true);
expect(mockHasAssetPermission).toHaveBeenCalledWith({
assetId: 'dash1',
assetType: 'dashboard_file',
userId: 'user123',
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: 'can_edit',
});
});
it('should handle null workspace sharing', async () => {
mockCheckDashboardsContainingMetric.mockResolvedValue([
{ id: 'dash1', organizationId: mockOrganizationId, workspaceSharing: null },
]);
mockHasAssetPermission.mockResolvedValue(false);
const result = await checkMetricDashboardAccess('metric123', mockUser as any);
expect(result).toBe(false);
expect(mockHasAssetPermission).toHaveBeenCalledWith({
assetId: 'dash1',
assetType: 'dashboard_file',
userId: 'user123',
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: 'none',
});
});
@ -85,7 +143,10 @@ describe('Cascading Permissions', () => {
});
it('should return false if user has no access to any dashboard', async () => {
mockCheckDashboardsContainingMetric.mockResolvedValue([{ id: 'dash1' }, { id: 'dash2' }]);
mockCheckDashboardsContainingMetric.mockResolvedValue([
{ id: 'dash1', organizationId: mockOrganizationId, workspaceSharing: 'none' },
{ id: 'dash2', organizationId: mockOrganizationId, workspaceSharing: 'none' },
]);
mockHasAssetPermission.mockResolvedValue(false);
@ -104,9 +165,66 @@ describe('Cascading Permissions', () => {
});
});
describe('checkMetricReportAccess', () => {
it('should check if user has access to reports containing the metric', async () => {
mockCheckReportsContainingMetric.mockResolvedValue([
{ id: 'report1', organizationId: mockOrganizationId, workspaceSharing: 'can_view' },
{ id: 'report2', organizationId: mockOrganizationId, workspaceSharing: 'none' },
]);
mockHasAssetPermission.mockResolvedValueOnce(true); // report1 - has access
const result = await checkMetricReportAccess('metric123', mockUser as any);
expect(result).toBe(true);
expect(mockCheckReportsContainingMetric).toHaveBeenCalledWith('metric123');
expect(mockHasAssetPermission).toHaveBeenCalledTimes(1); // Stops on first match
expect(mockHasAssetPermission).toHaveBeenCalledWith({
assetId: 'report1',
assetType: 'report_file',
userId: 'user123',
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: 'can_view',
});
});
it('should return false if no reports contain the metric', async () => {
mockCheckReportsContainingMetric.mockResolvedValue([]);
const result = await checkMetricReportAccess('metric123', mockUser as any);
expect(result).toBe(false);
expect(mockHasAssetPermission).not.toHaveBeenCalled();
});
it('should handle workspace sharing on reports', async () => {
mockCheckReportsContainingMetric.mockResolvedValue([
{ id: 'report1', organizationId: mockOrganizationId, workspaceSharing: 'full_access' },
]);
mockHasAssetPermission.mockResolvedValue(true);
const result = await checkMetricReportAccess('metric123', mockUser as any);
expect(result).toBe(true);
expect(mockHasAssetPermission).toHaveBeenCalledWith({
assetId: 'report1',
assetType: 'report_file',
userId: 'user123',
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: 'full_access',
});
});
});
describe('checkMetricChatAccess', () => {
it('should check if user has access to chats containing the metric', async () => {
mockCheckChatsContainingAsset.mockResolvedValue([{ id: 'chat1' }, { id: 'chat2' }]);
it('should check if user has access to chats containing the metric with workspace sharing', async () => {
mockCheckChatsContainingAsset.mockResolvedValue([
{ id: 'chat1', organizationId: mockOrganizationId, workspaceSharing: 'can_view' },
{ id: 'chat2', organizationId: mockOrganizationId, workspaceSharing: 'none' },
]);
mockHasAssetPermission.mockResolvedValueOnce(true); // chat1 - has access
@ -115,12 +233,45 @@ describe('Cascading Permissions', () => {
expect(result).toBe(true);
expect(mockCheckChatsContainingAsset).toHaveBeenCalledWith('metric123', 'metric');
expect(mockHasAssetPermission).toHaveBeenCalledTimes(1); // Stops on first match
expect(mockHasAssetPermission).toHaveBeenCalledWith({
assetId: 'chat1',
assetType: 'chat',
userId: 'user123',
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: 'can_view',
});
});
});
describe('checkMetricCollectionAccess', () => {
it('should check collections with workspace sharing', async () => {
mockCheckCollectionsContainingAsset.mockResolvedValue([
{ id: 'coll1', organizationId: mockOrganizationId, workspaceSharing: 'can_edit' },
]);
mockHasAssetPermission.mockResolvedValue(true);
const result = await checkMetricCollectionAccess('metric123', mockUser as any);
expect(result).toBe(true);
expect(mockCheckCollectionsContainingAsset).toHaveBeenCalledWith('metric123', 'metric');
expect(mockHasAssetPermission).toHaveBeenCalledWith({
assetId: 'coll1',
assetType: 'collection',
userId: 'user123',
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: 'can_edit',
});
});
});
describe('checkDashboardCollectionAccess', () => {
it('should check if user has access to collections containing the dashboard', async () => {
mockCheckCollectionsContainingAsset.mockResolvedValue([{ id: 'coll1' }]);
it('should check if user has access to collections containing the dashboard with workspace sharing', async () => {
mockCheckCollectionsContainingAsset.mockResolvedValue([
{ id: 'coll1', organizationId: mockOrganizationId, workspaceSharing: 'can_view' },
]);
mockHasAssetPermission.mockResolvedValue(true);
@ -133,6 +284,8 @@ describe('Cascading Permissions', () => {
assetType: 'collection',
userId: 'user123',
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: 'can_view',
});
});
});
@ -158,18 +311,23 @@ describe('Cascading Permissions', () => {
expect(mockCheckDashboardsContainingMetric).not.toHaveBeenCalled();
});
it('should check all paths for metrics', async () => {
it('should check all paths for metrics including reports', async () => {
// No dashboards
mockCheckDashboardsContainingMetric.mockResolvedValue([]);
// No chats
mockCheckChatsContainingAsset.mockResolvedValueOnce([]);
// Has collection access
mockCheckCollectionsContainingAsset.mockResolvedValue([{ id: 'coll1' }]);
mockCheckChatsContainingAsset.mockResolvedValue([]);
// No collections
mockCheckCollectionsContainingAsset.mockResolvedValue([]);
// Has report access
mockCheckReportsContainingMetric.mockResolvedValue([
{ id: 'report1', organizationId: mockOrganizationId, workspaceSharing: 'can_view' },
]);
mockHasAssetPermission.mockResolvedValue(true);
const result = await checkCascadingPermissions('metric123', 'metric', mockUser as any);
expect(result).toBe(true);
expect(mockCheckReportsContainingMetric).toHaveBeenCalledWith('metric123');
expect(mockSetCachedCascadingPermission).toHaveBeenCalledWith(
'user123',
'metric123',
@ -178,9 +336,24 @@ describe('Cascading Permissions', () => {
);
});
it('should check dashboard cascading paths', async () => {
// Has chat access
mockCheckChatsContainingAsset.mockResolvedValue([{ id: 'chat1' }]);
it('should check metric_file type same as metric', async () => {
// Has dashboard access with workspace sharing
mockCheckDashboardsContainingMetric.mockResolvedValue([
{ id: 'dash1', organizationId: mockOrganizationId, workspaceSharing: 'full_access' },
]);
mockHasAssetPermission.mockResolvedValue(true);
const result = await checkCascadingPermissions('metric123', 'metric_file', mockUser as any);
expect(result).toBe(true);
expect(mockCheckDashboardsContainingMetric).toHaveBeenCalledWith('metric123');
});
it('should check dashboard cascading paths with workspace sharing', async () => {
// Has chat access with workspace sharing
mockCheckChatsContainingAsset.mockResolvedValue([
{ id: 'chat1', organizationId: mockOrganizationId, workspaceSharing: 'can_edit' },
]);
mockHasAssetPermission.mockResolvedValue(true);
const result = await checkCascadingPermissions('dash123', 'dashboard', mockUser as any);
@ -189,6 +362,22 @@ describe('Cascading Permissions', () => {
expect(mockCheckChatsContainingAsset).toHaveBeenCalledWith('dash123', 'dashboard');
});
it('should check dashboard_file type same as dashboard', async () => {
// No chat access
mockCheckChatsContainingAsset.mockResolvedValue([]);
// Has collection access
mockCheckCollectionsContainingAsset.mockResolvedValue([
{ id: 'coll1', organizationId: mockOrganizationId, workspaceSharing: 'can_view' },
]);
mockHasAssetPermission.mockResolvedValue(true);
const result = await checkCascadingPermissions('dash123', 'dashboard_file', mockUser as any);
expect(result).toBe(true);
expect(mockCheckChatsContainingAsset).toHaveBeenCalledWith('dash123', 'dashboard');
expect(mockCheckCollectionsContainingAsset).toHaveBeenCalledWith('dash123', 'dashboard');
});
it('should check chat cascading paths', async () => {
// No collection access
mockCheckCollectionsContainingAsset.mockResolvedValue([]);
@ -212,6 +401,14 @@ describe('Cascading Permissions', () => {
expect(mockCheckCollectionsContainingAsset).not.toHaveBeenCalled();
});
it('should return false for report_file (no cascading)', async () => {
const result = await checkCascadingPermissions('report123', 'report_file', mockUser as any);
expect(result).toBe(false);
// Should not call any check functions
expect(mockCheckReportsContainingMetric).not.toHaveBeenCalled();
});
it('should return false for unknown asset types', async () => {
const result = await checkCascadingPermissions(
'unknown123',
@ -226,6 +423,7 @@ describe('Cascading Permissions', () => {
mockCheckDashboardsContainingMetric.mockResolvedValue([]);
mockCheckChatsContainingAsset.mockResolvedValue([]);
mockCheckCollectionsContainingAsset.mockResolvedValue([]);
mockCheckReportsContainingMetric.mockResolvedValue([]);
const result = await checkCascadingPermissions('metric123', 'metric', mockUser as any);
@ -247,5 +445,84 @@ describe('Cascading Permissions', () => {
expect(mockSetCachedCascadingPermission).not.toHaveBeenCalled();
});
it('should stop checking on first successful access path', async () => {
// Has dashboard access (first check)
mockCheckDashboardsContainingMetric.mockResolvedValue([
{ id: 'dash1', organizationId: mockOrganizationId, workspaceSharing: 'can_view' },
]);
mockHasAssetPermission.mockResolvedValue(true);
const result = await checkCascadingPermissions('metric123', 'metric', mockUser as any);
expect(result).toBe(true);
// Should not check other paths
expect(mockCheckChatsContainingAsset).not.toHaveBeenCalled();
expect(mockCheckCollectionsContainingAsset).not.toHaveBeenCalled();
expect(mockCheckReportsContainingMetric).not.toHaveBeenCalled();
});
});
describe('Workspace Sharing Edge Cases', () => {
it('should handle mixed workspace sharing levels', async () => {
mockCheckDashboardsContainingMetric.mockResolvedValue([
{ id: 'dash1', organizationId: mockOrganizationId, workspaceSharing: 'none' },
{ id: 'dash2', organizationId: mockOrganizationId, workspaceSharing: 'can_view' },
{ id: 'dash3', organizationId: mockOrganizationId, workspaceSharing: 'can_edit' },
{ id: 'dash4', organizationId: mockOrganizationId, workspaceSharing: 'full_access' },
]);
mockHasAssetPermission
.mockResolvedValueOnce(false) // dash1 - no access (none)
.mockResolvedValueOnce(false) // dash2 - no access (can_view)
.mockResolvedValueOnce(true); // dash3 - has access (can_edit)
const result = await checkMetricDashboardAccess('metric123', mockUser as any);
expect(result).toBe(true);
expect(mockHasAssetPermission).toHaveBeenCalledTimes(3); // Stops on first success
// Verify each call had the correct workspace sharing
expect(mockHasAssetPermission).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ workspaceSharing: 'none' })
);
expect(mockHasAssetPermission).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ workspaceSharing: 'can_view' })
);
expect(mockHasAssetPermission).toHaveBeenNthCalledWith(
3,
expect.objectContaining({ workspaceSharing: 'can_edit' })
);
});
it('should handle assets in multiple organizations', async () => {
const org1 = 'org1';
const org2 = 'org2';
mockCheckDashboardsContainingMetric.mockResolvedValue([
{ id: 'dash1', organizationId: org1, workspaceSharing: 'can_view' },
{ id: 'dash2', organizationId: org2, workspaceSharing: 'can_view' },
]);
mockHasAssetPermission
.mockResolvedValueOnce(false) // dash1 in org1 - no access
.mockResolvedValueOnce(true); // dash2 in org2 - has access
const result = await checkMetricDashboardAccess('metric123', mockUser as any);
expect(result).toBe(true);
// Verify different org IDs were passed
expect(mockHasAssetPermission).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ organizationId: org1 })
);
expect(mockHasAssetPermission).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ organizationId: org2 })
);
});
});
});

View File

@ -58,6 +58,7 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
| 'chat'
| 'metric_file'
| 'dashboard_file'
| 'report_file'
| 'collection',
};
if (organizationId !== undefined) {

View File

@ -0,0 +1,102 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { checkDashboardsContainingMetric } from './check-dashboards-containing-metric';
// Mock the database connection
vi.mock('../../connection', () => ({
db: {
select: vi.fn(),
},
}));
// Mock drizzle-orm
vi.mock('drizzle-orm', async (importOriginal) => {
const actual = (await importOriginal()) as any;
return {
...actual,
sql: actual.sql,
and: vi.fn((...conditions) => ({ _and: conditions })),
eq: vi.fn((a, b) => ({ _eq: [a, b] })),
isNull: vi.fn((field) => ({ _isNull: field })),
};
});
describe('checkDashboardsContainingMetric', () => {
const mockDb = {
select: vi.fn(),
};
const mockQueryChain = {
from: vi.fn(),
innerJoin: vi.fn(),
where: vi.fn(),
};
beforeEach(async () => {
vi.clearAllMocks();
// Setup the mock chain
mockDb.select.mockReturnValue(mockQueryChain);
mockQueryChain.from.mockReturnValue(mockQueryChain);
mockQueryChain.innerJoin.mockReturnValue(mockQueryChain);
mockQueryChain.where.mockReturnValue(Promise.resolve([]));
// Set up the mock db
const connection = await import('../../connection');
vi.mocked(connection.db).select = mockDb.select;
});
it('should return dashboards with organizationId and workspaceSharing', async () => {
const mockDashboards = [
{ id: 'dash1', organizationId: 'org1', workspaceSharing: 'can_view' },
{ id: 'dash2', organizationId: 'org2', workspaceSharing: 'none' },
{ id: 'dash3', organizationId: 'org1', workspaceSharing: null },
];
mockQueryChain.where.mockResolvedValue(mockDashboards);
const result = await checkDashboardsContainingMetric('metric123');
expect(result).toEqual(mockDashboards);
expect(mockDb.select).toHaveBeenCalledWith({
id: expect.anything(),
organizationId: expect.anything(),
workspaceSharing: expect.anything(),
});
expect(mockQueryChain.from).toHaveBeenCalled();
expect(mockQueryChain.innerJoin).toHaveBeenCalled();
expect(mockQueryChain.where).toHaveBeenCalled();
});
it('should return empty array when no dashboards contain the metric', async () => {
mockQueryChain.where.mockResolvedValue([]);
const result = await checkDashboardsContainingMetric('metric123');
expect(result).toEqual([]);
});
it('should handle null workspace sharing values', async () => {
const mockDashboards = [{ id: 'dash1', organizationId: 'org1', workspaceSharing: null }];
mockQueryChain.where.mockResolvedValue(mockDashboards);
const result = await checkDashboardsContainingMetric('metric123');
expect(result).toEqual([{ id: 'dash1', organizationId: 'org1', workspaceSharing: null }]);
});
it('should handle all workspace sharing levels', async () => {
const mockDashboards = [
{ id: 'dash1', organizationId: 'org1', workspaceSharing: 'none' },
{ id: 'dash2', organizationId: 'org1', workspaceSharing: 'can_view' },
{ id: 'dash3', organizationId: 'org1', workspaceSharing: 'can_edit' },
{ id: 'dash4', organizationId: 'org1', workspaceSharing: 'full_access' },
];
mockQueryChain.where.mockResolvedValue(mockDashboards);
const result = await checkDashboardsContainingMetric('metric123');
expect(result).toEqual(mockDashboards);
});
});

View File

@ -0,0 +1,117 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { checkReportsContainingMetric } from './check-reports-containing-metric';
// Mock the database connection
vi.mock('../../connection', () => ({
db: {
select: vi.fn(),
},
}));
// Mock drizzle-orm
vi.mock('drizzle-orm', async (importOriginal) => {
const actual = (await importOriginal()) as any;
return {
...actual,
sql: actual.sql,
and: vi.fn((...conditions) => ({ _and: conditions })),
eq: vi.fn((a, b) => ({ _eq: [a, b] })),
isNull: vi.fn((field) => ({ _isNull: field })),
};
});
describe('checkReportsContainingMetric', () => {
const mockDb = {
select: vi.fn(),
};
const mockQueryChain = {
from: vi.fn(),
innerJoin: vi.fn(),
where: vi.fn(),
};
beforeEach(async () => {
vi.clearAllMocks();
// Setup the mock chain
mockDb.select.mockReturnValue(mockQueryChain);
mockQueryChain.from.mockReturnValue(mockQueryChain);
mockQueryChain.innerJoin.mockReturnValue(mockQueryChain);
mockQueryChain.where.mockReturnValue(Promise.resolve([]));
// Set up the mock db
const connection = await import('../../connection');
vi.mocked(connection.db).select = mockDb.select;
});
it('should return reports with organizationId and workspaceSharing', async () => {
const mockReports = [
{ id: 'report1', organizationId: 'org1', workspaceSharing: 'can_view' },
{ id: 'report2', organizationId: 'org2', workspaceSharing: 'full_access' },
{ id: 'report3', organizationId: 'org1', workspaceSharing: null },
];
mockQueryChain.where.mockResolvedValue(mockReports);
const result = await checkReportsContainingMetric('metric123');
expect(result).toEqual(mockReports);
expect(mockDb.select).toHaveBeenCalledWith({
id: expect.anything(),
organizationId: expect.anything(),
workspaceSharing: expect.anything(),
});
expect(mockQueryChain.from).toHaveBeenCalled();
expect(mockQueryChain.innerJoin).toHaveBeenCalled();
expect(mockQueryChain.where).toHaveBeenCalled();
});
it('should return empty array when no reports contain the metric', async () => {
mockQueryChain.where.mockResolvedValue([]);
const result = await checkReportsContainingMetric('metric123');
expect(result).toEqual([]);
});
it('should handle null workspace sharing values', async () => {
const mockReports = [{ id: 'report1', organizationId: 'org1', workspaceSharing: null }];
mockQueryChain.where.mockResolvedValue(mockReports);
const result = await checkReportsContainingMetric('metric123');
expect(result).toEqual([{ id: 'report1', organizationId: 'org1', workspaceSharing: null }]);
});
it('should handle all workspace sharing levels', async () => {
const mockReports = [
{ id: 'report1', organizationId: 'org1', workspaceSharing: 'none' },
{ id: 'report2', organizationId: 'org1', workspaceSharing: 'can_view' },
{ id: 'report3', organizationId: 'org1', workspaceSharing: 'can_edit' },
{ id: 'report4', organizationId: 'org1', workspaceSharing: 'full_access' },
];
mockQueryChain.where.mockResolvedValue(mockReports);
const result = await checkReportsContainingMetric('metric123');
expect(result).toEqual(mockReports);
});
it('should filter out deleted reports and deleted relationships', async () => {
// This test validates that the query conditions are set up correctly
// In a real implementation, deleted items would be filtered by the database
const activeReports = [{ id: 'report1', organizationId: 'org1', workspaceSharing: 'can_view' }];
mockQueryChain.where.mockResolvedValue(activeReports);
const result = await checkReportsContainingMetric('metric123');
expect(result).toEqual(activeReports);
// Verify that the where clause was called (which would include isNull checks)
expect(mockQueryChain.where).toHaveBeenCalled();
});
});