Skip to main content
Glama

Google Ads MCP Server

by martechery
auth-oauth-device.real.test.ts7.73 kB
import 'dotenv/config'; import { describe, it, expect, beforeAll, afterEach, vi } from 'vitest'; import { registerTools } from '../../../src/server-tools.js'; import { extractCurrentAuthDetails, createTestAuthEnv, type ExtractedAuthDetails } from '../../utils/auth-extractor.js'; type ToolHandler = (input?: any) => Promise<any> | any; class LiveServer { public tools: Record<string, ToolHandler> = {}; tool(def: any, handler: ToolHandler) { this.tools[def.name] = handler; } } describe('Auth Flow: OAuth Device Flow', () => { let authDetails: ExtractedAuthDetails; let cleanup: (() => void) | null = null; const hasRequiredEnv = !!process.env.GOOGLE_ADS_DEVELOPER_TOKEN && !!process.env.GOOGLE_ADS_ACCOUNT_ID; const hasOAuthCreds = !!process.env.GOOGLE_OAUTH_CLIENT_ID && !!process.env.GOOGLE_OAUTH_CLIENT_SECRET; beforeAll(async () => { authDetails = await extractCurrentAuthDetails(); }); afterEach(() => { if (cleanup) { cleanup(); cleanup = null; } vi.restoreAllMocks(); }); it('checks OAuth client credentials availability', async () => { if (!hasOAuthCreds) { console.log('Skipping OAuth tests: GOOGLE_OAUTH_CLIENT_ID/SECRET not set'); return; } expect(process.env.GOOGLE_OAUTH_CLIENT_ID).toBeTruthy(); expect(process.env.GOOGLE_OAUTH_CLIENT_SECRET).toBeTruthy(); expect(process.env.GOOGLE_OAUTH_CLIENT_ID).toMatch(/\.apps\.googleusercontent\.com$/); }); it('simulates OAuth device flow creation', async () => { if (!hasRequiredEnv || !hasOAuthCreds) return; // Mock the OAuth device flow to simulate successful authentication vi.mock('../../../src/tools/oauth.js', () => ({ runDeviceOAuthForAds: vi.fn(async () => ({ path: '/tmp/test-adc.json', type: 'authorized_user', client_id: process.env.GOOGLE_OAUTH_CLIENT_ID, refresh_token: 'mock-refresh-token', access_token: authDetails.token, // Use real token for testing })), })); const server = new LiveServer(); registerTools(server as any); // Test OAuth login action const res = await server.tools['manage_auth']({ action: 'oauth_login' }); const text = res.content[0].text as string; expect(text).toContain('OAuth Device Flow Results:'); expect(text).toContain('Saved ADC authorized_user JSON:'); expect(text).toContain('/tmp/test-adc.json'); expect(text).toContain('Ads scope check after OAuth: OK'); }, 30_000); it('handles missing OAuth credentials gracefully', async () => { if (!hasRequiredEnv) return; // Test without OAuth credentials cleanup = createTestAuthEnv({ GOOGLE_OAUTH_CLIENT_ID: undefined, GOOGLE_OAUTH_CLIENT_SECRET: undefined, }); const server = new LiveServer(); registerTools(server as any); const res = await server.tools['manage_auth']({ action: 'oauth_login' }); const text = res.content[0].text as string; expect(text).toContain('OAuth credentials not available'); expect(text).toContain('GOOGLE_OAUTH_CLIENT_ID'); expect(text).toContain('GOOGLE_OAUTH_CLIENT_SECRET'); }, 15_000); it('validates OAuth scope requirements', async () => { if (!hasRequiredEnv || !hasOAuthCreds) return; // The OAuth flow should request correct scopes // We can check the oauth.ts module directly const oauth = await import('../../../src/tools/oauth.js'); // This test verifies the scopes are correctly defined // The actual scopes are hardcoded in the module expect(oauth).toBeDefined(); }); it('tests OAuth token refresh capability', async () => { if (!hasRequiredEnv || !hasOAuthCreds || !authDetails.hasValidADC) return; // Mock OAuth flow that returns refresh token vi.mock('../../../src/tools/oauth.js', () => ({ runDeviceOAuthForAds: vi.fn(async () => ({ path: '/tmp/test-adc-with-refresh.json', type: 'authorized_user', client_id: process.env.GOOGLE_OAUTH_CLIENT_ID, client_secret: process.env.GOOGLE_OAUTH_CLIENT_SECRET, refresh_token: 'mock-refresh-token-' + Date.now(), access_token: authDetails.token, })), })); const server = new LiveServer(); registerTools(server as any); const res = await server.tools['manage_auth']({ action: 'oauth_login' }); const text = res.content[0].text as string; expect(text).toContain('refresh_token'); expect(text).toContain('authorized_user'); // Should verify the token works with Google Ads API expect(text).toContain('Ads scope check after OAuth: OK'); }, 30_000); it('verifies OAuth ADC file creation structure', async () => { if (!hasRequiredEnv || !hasOAuthCreds) return; // Mock OAuth to return structured ADC data const mockAdcData = { type: 'authorized_user', client_id: process.env.GOOGLE_OAUTH_CLIENT_ID, client_secret: process.env.GOOGLE_OAUTH_CLIENT_SECRET, refresh_token: 'mock-refresh-token', access_token: 'mock-access-token', }; vi.mock('../../../src/tools/oauth.js', () => ({ runDeviceOAuthForAds: vi.fn(async () => ({ path: '/tmp/test-structured-adc.json', ...mockAdcData, })), })); const server = new LiveServer(); registerTools(server as any); const res = await server.tools['manage_auth']({ action: 'oauth_login' }); const text = res.content[0].text as string; // Should show structured ADC file was created expect(text).toContain('type: authorized_user'); expect(text).toContain('client_id:'); expect(text).toContain(process.env.GOOGLE_OAUTH_CLIENT_ID); }, 30_000); it('handles OAuth flow interruption gracefully', async () => { if (!hasRequiredEnv || !hasOAuthCreds) return; // Mock OAuth flow that fails/is interrupted vi.mock('../../../src/tools/oauth.js', () => ({ runDeviceOAuthForAds: vi.fn(async () => { throw new Error('User cancelled OAuth flow'); }), })); const server = new LiveServer(); registerTools(server as any); const res = await server.tools['manage_auth']({ action: 'oauth_login' }); const text = res.content[0].text as string; expect(text).toContain('Error during OAuth'); expect(text).toContain('User cancelled OAuth flow'); }, 30_000); it('validates OAuth with real Google Ads API call', async () => { if (!hasRequiredEnv || !hasOAuthCreds || !authDetails.hasValidADC) return; // Mock successful OAuth that creates working ADC file vi.mock('../../../src/tools/oauth.js', () => ({ runDeviceOAuthForAds: vi.fn(async () => ({ path: '/tmp/working-oauth-adc.json', type: 'authorized_user', client_id: process.env.GOOGLE_OAUTH_CLIENT_ID, refresh_token: 'mock-refresh-token', access_token: authDetails.token, // Use real token })), })); // Also mock the list accounts call to verify OAuth token works vi.mock('../../../src/tools/accounts.js', () => ({ listAccessibleCustomers: vi.fn().mockResolvedValue({ ok: true, status: 200, data: { resourceNames: ['customers/123456789'] } }), })); const server = new LiveServer(); registerTools(server as any); const res = await server.tools['manage_auth']({ action: 'oauth_login' }); const text = res.content[0].text as string; // Should show OAuth completed and API call succeeded expect(text).toContain('OAuth Device Flow Results:'); expect(text).toContain('Ads scope check after OAuth: OK'); }, 30_000); });

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/martechery/mcp-google-ads-ts'

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