Skip to main content
Glama
resourceAccessChecker.test.ts22.8 kB
import { getConfig } from '../config.js'; import { Server } from '../server.js'; import { mockDatasources } from './listDatasources/mockDatasources.js'; import { exportedForTesting } from './resourceAccessChecker.js'; import { mockView } from './views/mockView.js'; import { mockWorkbook } from './workbooks/mockWorkbook.js'; const { createResourceAccessChecker } = exportedForTesting; const mocks = vi.hoisted(() => ({ mockGetView: vi.fn(), mockGetWorkbook: vi.fn(), mockQueryDatasource: vi.fn(), })); vi.mock('../restApiInstance.js', () => ({ useRestApi: vi.fn().mockImplementation(async ({ callback }) => callback({ viewsMethods: { getView: mocks.mockGetView, }, workbooksMethods: { getWorkbook: mocks.mockGetWorkbook, }, datasourcesMethods: { queryDatasource: mocks.mockQueryDatasource, }, siteId: 'test-site-id', }), ), })); describe('ResourceAccessChecker', () => { const restApiArgs = { config: getConfig(), requestId: 'request-id', server: getServer() }; beforeEach(() => { vi.clearAllMocks(); }); describe('isDatasourceAllowed', () => { describe('allowed', () => { it('should return allowed when the datasource LUID is allowed by the datasources in the bounded context', async () => { const mockDatasource = mockDatasources.datasources[0]; const resourceAccessChecker = createResourceAccessChecker({ projectIds: null, datasourceIds: new Set([mockDatasource.id]), workbookIds: null, }); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: true }); // Check again to exercise the cache. expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: true }); expect(mocks.mockQueryDatasource).not.toHaveBeenCalled(); }); it('should return allowed when the datasource exists in a project that is allowed by the projects in the bounded context', async () => { const mockDatasource = mockDatasources.datasources[0]; mocks.mockQueryDatasource.mockResolvedValue(mockDatasource); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set([mockDatasource.project.id]), datasourceIds: null, workbookIds: null, }); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: true }); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: true }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Query Datasource" API each time. expect(mocks.mockQueryDatasource).toHaveBeenCalledTimes(2); }); it('should return allowed when the datasource is allowed by the datasources in the bounded context and exists in a project that is allowed by the projects in the bounded context', async () => { const mockDatasource = mockDatasources.datasources[0]; mocks.mockQueryDatasource.mockResolvedValue(mockDatasource); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set([mockDatasource.project.id]), datasourceIds: new Set([mockDatasource.id]), workbookIds: null, }); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: true }); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: true }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Query Datasource" API each time. expect(mocks.mockQueryDatasource).toHaveBeenCalledTimes(2); }); }); describe('not allowed', () => { it('should return not allowed when the datasource LUID is not allowed by the datasources in the bounded context', async () => { const mockDatasource = mockDatasources.datasources[0]; const resourceAccessChecker = createResourceAccessChecker({ projectIds: null, datasourceIds: new Set(['some-datasource-luid']), workbookIds: null, }); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: false, message: [ 'The set of allowed data sources that can be queried is limited by the server configuration.', `Querying the datasource with LUID ${mockDatasource.id} is not allowed.`, ].join(' '), }); }); it('should return not allowed when the datasource exists in a project that is not allowed by the projects in the bounded context', async () => { const mockDatasource = mockDatasources.datasources[0]; mocks.mockQueryDatasource.mockResolvedValue(mockDatasource); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set(['some-project-id']), datasourceIds: null, workbookIds: null, }); const expectedMessage = [ 'The set of allowed data sources that can be queried is limited by the server configuration.', `The datasource with LUID ${mockDatasource.id} cannot be queried because it does not belong to an allowed project.`, ].join(' '); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Query Datasource" API each time. expect(mocks.mockQueryDatasource).toHaveBeenCalledTimes(2); }); it('should return not allowed when the datasource is allowed by the datasources in the bounded context but exists in a project that is not allowed by the projects in the bounded context', async () => { const mockDatasource = mockDatasources.datasources[0]; mocks.mockQueryDatasource.mockResolvedValue(mockDatasource); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set(['some-project-id']), datasourceIds: new Set([mockDatasource.id]), workbookIds: null, }); const expectedMessage = [ 'The set of allowed data sources that can be queried is limited by the server configuration.', `The datasource with LUID ${mockDatasource.id} cannot be queried because it does not belong to an allowed project.`, ].join(' '); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); expect( await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid: mockDatasource.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Query Datasource" API each time. expect(mocks.mockQueryDatasource).toHaveBeenCalledTimes(2); }); }); }); describe('isWorkbookAllowed', () => { describe('allowed', () => { it('should return allowed when the workbook ID is allowed by the workbooks in the bounded context', async () => { const resourceAccessChecker = createResourceAccessChecker({ projectIds: null, datasourceIds: null, workbookIds: new Set([mockWorkbook.id]), }); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: true }); // Check again to exercise the cache. expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: true }); expect(mocks.mockGetWorkbook).not.toHaveBeenCalled(); }); it('should return allowed when the workbook is in a project that is allowed by the projects in the bounded context', async () => { mocks.mockGetWorkbook.mockResolvedValue(mockWorkbook); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set([mockWorkbook.project.id]), datasourceIds: null, workbookIds: null, }); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: true, content: mockWorkbook }); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: true, content: mockWorkbook }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Get Workbook" API each time. expect(mocks.mockGetWorkbook).toHaveBeenCalledTimes(2); }); it('should return allowed when the workbook is allowed by the workbooks in the bounded context and exists in a project that is allowed by the projects in the bounded context', async () => { mocks.mockGetWorkbook.mockResolvedValue(mockWorkbook); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set([mockWorkbook.project.id]), datasourceIds: null, workbookIds: new Set([mockWorkbook.id]), }); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: true, content: mockWorkbook }); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: true, content: mockWorkbook }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Get Workbook" API each time. expect(mocks.mockGetWorkbook).toHaveBeenCalledTimes(2); }); }); describe('not allowed', () => { it('should return not allowed when the workbook ID is not allowed by the workbooks in the bounded context', async () => { const resourceAccessChecker = createResourceAccessChecker({ projectIds: null, datasourceIds: null, workbookIds: new Set(['some-workbook-id']), }); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: false, message: [ 'The set of allowed workbooks that can be queried is limited by the server configuration.', `Querying the workbook with LUID ${mockWorkbook.id} is not allowed.`, ].join(' '), }); // Check again to exercise the cache. expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: false, message: [ 'The set of allowed workbooks that can be queried is limited by the server configuration.', `Querying the workbook with LUID ${mockWorkbook.id} is not allowed.`, ].join(' '), }); expect(mocks.mockGetWorkbook).not.toHaveBeenCalled(); }); it('should return not allowed when the workbook is in a project that is not allowed by the projects in the bounded context', async () => { mocks.mockGetWorkbook.mockResolvedValue(mockWorkbook); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set(['some-project-id']), datasourceIds: null, workbookIds: null, }); const expectedMessage = [ 'The set of allowed workbooks that can be queried is limited by the server configuration.', `The workbook with LUID ${mockWorkbook.id} cannot be queried because it does not belong to an allowed project.`, ].join(' '); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Get Workbook" API each time. expect(mocks.mockGetWorkbook).toHaveBeenCalledTimes(2); }); it('should return not allowed when the workbook is allowed by the workbooks in the bounded context and exists in a project that is not allowed by the projects in the bounded context', async () => { mocks.mockGetWorkbook.mockResolvedValue(mockWorkbook); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set(['some-project-id']), datasourceIds: null, workbookIds: new Set([mockWorkbook.id]), }); const expectedMessage = [ 'The set of allowed workbooks that can be queried is limited by the server configuration.', `The workbook with LUID ${mockWorkbook.id} cannot be queried because it does not belong to an allowed project.`, ].join(' '); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); expect( await resourceAccessChecker.isWorkbookAllowed({ workbookId: mockWorkbook.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Get Workbook" API each time. expect(mocks.mockGetWorkbook).toHaveBeenCalledTimes(2); }); }); }); describe('isViewAllowed', () => { describe('allowed', () => { it('should return allowed when the view exists in a workbook that is allowed by the workbooks in the bounded context', async () => { mocks.mockGetView.mockResolvedValue(mockView); const resourceAccessChecker = createResourceAccessChecker({ projectIds: null, datasourceIds: null, workbookIds: new Set([mockWorkbook.id]), }); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: true }); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: true }); // Since project filtering is not enabled, we cached the result so we only need to call the "Get View" API once. expect(mocks.mockGetView).toHaveBeenCalledOnce(); }); it('should return allowed when the view exists in a workbook that is allowed by the projects in the bounded context', async () => { mocks.mockGetView.mockResolvedValue(mockView); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set([mockView.project.id]), datasourceIds: null, workbookIds: null, }); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: true }); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: true }); // Since project filtering is enabled, we can't cache the result and we need to call the "Get View" API each time. expect(mocks.mockGetView).toHaveBeenCalledTimes(2); }); it('should return allowed when the view exists in a workbook that is allowed by the workbooks and the projects in the bounded context', async () => { mocks.mockGetView.mockResolvedValue(mockView); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set([mockView.project.id]), datasourceIds: null, workbookIds: new Set([mockWorkbook.id]), }); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: true }); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: true }); // Since project filtering is enabled, we can't cache the result and we need to call the "Get View" API each time. expect(mocks.mockGetView).toHaveBeenCalledTimes(2); }); }); describe('not allowed', () => { it('should return not allowed when the view exists in a workbook that is not allowed by the workbooks in the bounded context', async () => { mocks.mockGetView.mockResolvedValue(mockView); const resourceAccessChecker = createResourceAccessChecker({ projectIds: null, datasourceIds: null, workbookIds: new Set(['some-workbook-id']), }); const expectedMessage = [ 'The set of allowed workbooks that can be queried is limited by the server configuration.', `The view with LUID ${mockView.id} cannot be queried because it does not belong to an allowed workbook.`, ].join(' '); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); // Since project filtering is not enabled, we can cache the result so we only need to call the "Get View" API once. expect(mocks.mockGetView).toHaveBeenCalledTimes(1); }); it('should return not allowed when the view exists in a workbook that is not allowed by the projects in the bounded context', async () => { mocks.mockGetView.mockResolvedValue(mockView); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set(['some-project-id']), datasourceIds: null, workbookIds: null, }); const expectedMessage = [ 'The set of allowed views that can be queried is limited by the server configuration.', `The view with LUID ${mockView.id} cannot be queried because it does not belong to an allowed project.`, ].join(' '); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Get View" API each time. expect(mocks.mockGetView).toHaveBeenCalledTimes(2); }); it('should return not allowed when the view exists in a workbook that is allowed in the bounded context but exists in a project that is not allowed by the projects in the bounded context', async () => { mocks.mockGetView.mockResolvedValue(mockView); const resourceAccessChecker = createResourceAccessChecker({ projectIds: new Set(['some-project-id']), datasourceIds: null, workbookIds: new Set([mockWorkbook.id]), }); const expectedMessage = [ 'The set of allowed views that can be queried is limited by the server configuration.', `The view with LUID ${mockView.id} cannot be queried because it does not belong to an allowed project.`, ].join(' '); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); expect( await resourceAccessChecker.isViewAllowed({ viewId: mockView.id, restApiArgs, }), ).toEqual({ allowed: false, message: expectedMessage, }); // Since project filtering is enabled, we cannot cache the result so we need to call the "Get View" API each time. expect(mocks.mockGetView).toHaveBeenCalledTimes(2); }); }); }); }); function getServer(): InstanceType<typeof Server> { const server = new Server(); server.tool = vi.fn(); return server; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/datalabs89/tableau-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server