mirror of https://github.com/buster-so/buster.git
checks and permission
This commit is contained in:
parent
d0659723f2
commit
d0c7efc441
|
@ -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 })
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -58,6 +58,7 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
|
|||
| 'chat'
|
||||
| 'metric_file'
|
||||
| 'dashboard_file'
|
||||
| 'report_file'
|
||||
| 'collection',
|
||||
};
|
||||
if (organizationId !== undefined) {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue