auth-oauth-device.real.test.ts•7.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);
});