Skip to main content
Glama
authorizationCodeCallback.test.ts5.89 kB
import express from 'express'; import http from 'http'; import request from 'supertest'; import { getConfig } from '../../src/config.js'; import { serverName } from '../../src/server.js'; import { startExpressServer } from '../../src/server/express.js'; import { resetEnv, setEnv } from './testEnv.js'; const mocks = vi.hoisted(() => ({ mockGetTokenResult: vi.fn(), })); vi.mock('../../src/sdks/tableau-oauth/methods.js', () => ({ getTokenResult: mocks.mockGetTokenResult, })); describe('authorization code callback', () => { let _server: http.Server | undefined; beforeAll(setEnv); afterAll(resetEnv); beforeEach(() => { vi.clearAllMocks(); _server = undefined; }); afterEach(async () => { await new Promise<void>((resolve) => { if (_server) { _server.close(() => { resolve(); }); } else { resolve(); } }); }); async function startServer(): Promise<{ app: express.Application }> { const { app, server } = await startExpressServer({ basePath: serverName, config: getConfig(), logLevel: 'info', }); _server = server; return { app }; } it('should reject if user denies authorization', async () => { const { app } = await startServer(); const response = await request(app).get('/Callback').query({ error: 'access_denied', }); expect(response.status).toBe(400); expect(response.headers['content-type']).toBe('application/json; charset=utf-8'); expect(response.body).toEqual({ error: 'access_denied', error_description: 'User denied authorization', }); }); it('should reject if state is invalid', async () => { const { app } = await startServer(); const response = await request(app).get('/Callback').query({ code: 'test-code', state: 'invalid-state', }); expect(response.status).toBe(400); expect(response.headers['content-type']).toBe('application/json; charset=utf-8'); expect(response.body).toEqual({ error: 'invalid_request', error_description: 'Invalid state parameter', }); }); it('should reject if the Tableau access token is not successfully retrieved', async () => { const { app } = await startServer(); const authzResponse = await request(app).get('/oauth/authorize').query({ client_id: 'test-client-id', redirect_uri: 'http://localhost:3000', response_type: 'code', code_challenge: 'test-code-challenge', code_challenge_method: 'S256', state: 'test-state', }); const authzLocation = new URL(authzResponse.headers['location']); const [authKey, tableauState] = authzLocation.searchParams.get('state')?.split(':') ?? []; mocks.mockGetTokenResult.mockImplementation(() => { throw new Error('Unauthorized'); }); const response = await request(app) .get('/Callback') .query({ code: 'test-code', state: `${authKey}:${tableauState}`, }); expect(response.status).toBe(400); expect(response.headers['content-type']).toBe('application/json; charset=utf-8'); expect(response.body).toEqual({ error: 'invalid_request', error_description: 'Failed to exchange authorization code', }); }); it('should reject if the originHost returned by Tableau does not match the expected server', async () => { const { app } = await startServer(); const authzResponse = await request(app).get('/oauth/authorize').query({ client_id: 'test-client-id', redirect_uri: 'http://localhost:3000', response_type: 'code', code_challenge: 'test-code-challenge', code_challenge_method: 'S256', state: 'test-state', }); const authzLocation = new URL(authzResponse.headers['location']); const [authKey, tableauState] = authzLocation.searchParams.get('state')?.split(':') ?? []; mocks.mockGetTokenResult.mockResolvedValue({ accessToken: 'test-access-token', refreshToken: 'test-refresh-token', expiresInSeconds: 3600, originHost: '10az.online.tableau.com', }); const response = await request(app) .get('/Callback') .query({ code: 'test-code', state: `${authKey}:${tableauState}`, }); expect(response.status).toBe(400); expect(response.headers['content-type']).toBe('application/json; charset=utf-8'); expect(response.body).toEqual({ error: 'invalid_request', error_description: 'Invalid origin host: 10az.online.tableau.com. Expected: 10ax.online.tableau.com', }); }); it('should issue an authorization code when the Tableau access token is successfully retrieved', async () => { const { app } = await startServer(); const authzResponse = await request(app).get('/oauth/authorize').query({ client_id: 'test-client-id', redirect_uri: 'http://localhost:3000', response_type: 'code', code_challenge: 'test-code-challenge', code_challenge_method: 'S256', state: 'test-state', }); const authzLocation = new URL(authzResponse.headers['location']); const [authKey, tableauState] = authzLocation.searchParams.get('state')?.split(':') ?? []; mocks.mockGetTokenResult.mockResolvedValue({ accessToken: 'test-access-token', refreshToken: 'test-refresh-token', expiresInSeconds: 3600, originHost: '10ax.online.tableau.com', }); const response = await request(app) .get('/Callback') .query({ code: 'test-code', state: `${authKey}:${tableauState}`, }); expect(response.status).toBe(302); const location = new URL(response.headers['location']); expect(location.origin).toBe('http://localhost:3000'); expect(location.searchParams.get('code')).not.toBeNull(); expect(location.searchParams.get('state')).toBe('test-state'); }); });

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