test-integration.tsā¢18.9 kB
/**
* Integration Tests for GoogleDocsMCP - AgenticLedger Platform
*
* This test suite performs REAL API calls to verify all MCP tools work correctly.
* It follows the AgenticLedger platform integration guidelines.
*
* Requirements:
* - Google Cloud Project with Docs API + Drive API enabled
* - OAuth 2.0 credentials configured (credentials.json)
* - Valid token.json (run server once to authorize)
* - .env file with TEST_DOCUMENT_ID (optional - will create if missing)
*/
import { google } from 'googleapis';
import { OAuth2Client } from 'google-auth-library';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { authorize } from './dist/auth.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Color codes for terminal output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
};
interface TestResult {
tool: string;
status: 'PASS' | 'FAIL' | 'SKIP';
duration: number;
request?: any;
response?: any;
error?: string;
}
const testResults: TestResult[] = [];
// Helper functions
function log(message: string, color = colors.reset) {
console.log(`${color}${message}${colors.reset}`);
}
function logSection(title: string) {
console.log('\n' + '='.repeat(80));
log(title, colors.bright + colors.cyan);
console.log('='.repeat(80) + '\n');
}
function logTest(testName: string) {
log(`\nš Testing: ${testName}`, colors.blue);
}
function logSuccess(message: string) {
log(`ā
${message}`, colors.green);
}
function logError(message: string) {
log(`ā ${message}`, colors.red);
}
function logWarning(message: string) {
log(`ā ļø ${message}`, colors.yellow);
}
// Initialize Google API clients
async function initializeClients() {
try {
const authClient = await authorize();
const docs = google.docs({ version: 'v1', auth: authClient });
const drive = google.drive({ version: 'v3', auth: authClient });
return { docs, drive, auth: authClient };
} catch (error: any) {
logError('Failed to initialize Google API clients');
logError(error.message);
throw error;
}
}
// Create a test document
async function createTestDocument(drive: any): Promise<string> {
logTest('Creating test document');
try {
const response = await drive.files.create({
requestBody: {
name: `AgenticLedger Test Doc ${new Date().toISOString()}`,
mimeType: 'application/vnd.google-apps.document',
},
fields: 'id,name',
});
const documentId = response.data.id;
logSuccess(`Test document created: ${documentId}`);
log(` Name: ${response.data.name}`, colors.cyan);
return documentId;
} catch (error: any) {
logError(`Failed to create test document: ${error.message}`);
throw error;
}
}
// Test suite
async function runTests() {
logSection('GoogleDocsMCP Integration Tests - AgenticLedger Platform');
const startTime = Date.now();
const { docs, drive, auth } = await initializeClients();
log(`Authenticated as: ${(auth as OAuth2Client).credentials}`, colors.cyan);
// Create or use existing test document
let testDocumentId: string;
if (process.env.TEST_DOCUMENT_ID) {
testDocumentId = process.env.TEST_DOCUMENT_ID;
log(`Using existing test document: ${testDocumentId}`, colors.cyan);
} else {
testDocumentId = await createTestDocument(drive);
}
// Test 1: readGoogleDoc
await testReadDocument(docs, testDocumentId);
// Test 2: appendToGoogleDoc
await testAppendToDocument(docs, testDocumentId);
// Test 3: insertText
await testInsertText(docs, testDocumentId);
// Test 4: deleteRange
await testDeleteRange(docs, testDocumentId);
// Test 5: applyTextStyle
await testApplyTextStyle(docs, testDocumentId);
// Test 6: applyParagraphStyle
await testApplyParagraphStyle(docs, testDocumentId);
// Test 7: insertTable
await testInsertTable(docs, testDocumentId);
// Test 8: insertPageBreak
await testInsertPageBreak(docs, testDocumentId);
// Test 9: listDocumentTabs
await testListDocumentTabs(docs, testDocumentId);
// Test 10: listGoogleDocs (Drive API)
await testListDocuments(drive);
// Test 11: getDocumentInfo (Drive API)
await testGetDocumentInfo(drive, testDocumentId);
// Test 12: createDocument (Drive API)
await testCreateDocument(drive);
// Print summary
printSummary(startTime, testDocumentId);
}
async function testReadDocument(docs: any, documentId: string) {
logTest('readGoogleDoc');
const startTime = Date.now();
try {
const request = { documentId, fields: '*' };
const response = await docs.documents.get(request);
const duration = Date.now() - startTime;
const result: TestResult = {
tool: 'readGoogleDoc',
status: 'PASS',
duration,
request,
response: {
title: response.data.title,
revisionId: response.data.revisionId,
documentId: response.data.documentId,
},
};
testResults.push(result);
logSuccess(`Document read (${duration}ms)`);
log(` Title: ${response.data.title}`, colors.cyan);
log(` Revision: ${response.data.revisionId}`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'readGoogleDoc',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testAppendToDocument(docs: any, documentId: string) {
logTest('appendToGoogleDoc');
const startTime = Date.now();
try {
// Get current end index
const doc = await docs.documents.get({ documentId, fields: 'body' });
const endIndex = doc.data.body.content[doc.data.body.content.length - 1].endIndex - 1;
const textToAppend = `\n\nTest appended text at ${new Date().toISOString()}`;
const request = {
documentId,
requests: [{
insertText: {
location: { index: endIndex },
text: textToAppend,
},
}],
};
const response = await docs.documents.batchUpdate(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'appendToGoogleDoc',
status: 'PASS',
duration,
request,
response: {
documentId: response.data.documentId,
},
});
logSuccess(`Text appended (${duration}ms)`);
log(` Text: "${textToAppend.substring(0, 50)}..."`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'appendToGoogleDoc',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testInsertText(docs: any, documentId: string) {
logTest('insertText');
const startTime = Date.now();
try {
const textToInsert = `\nInserted text at ${new Date().toISOString()}\n`;
const request = {
documentId,
requests: [{
insertText: {
location: { index: 1 },
text: textToInsert,
},
}],
};
const response = await docs.documents.batchUpdate(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'insertText',
status: 'PASS',
duration,
request,
response: {
documentId: response.data.documentId,
},
});
logSuccess(`Text inserted (${duration}ms)`);
log(` Text: "${textToInsert.substring(0, 50)}..."`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'insertText',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testDeleteRange(docs: any, documentId: string) {
logTest('deleteRange');
const startTime = Date.now();
try {
// Delete a small range (index 10-15)
const request = {
documentId,
requests: [{
deleteContentRange: {
range: {
startIndex: 10,
endIndex: 15,
},
},
}],
};
const response = await docs.documents.batchUpdate(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'deleteRange',
status: 'PASS',
duration,
request,
response: {
documentId: response.data.documentId,
},
});
logSuccess(`Range deleted (${duration}ms)`);
log(` Range: 10-15`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'deleteRange',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testApplyTextStyle(docs: any, documentId: string) {
logTest('applyTextStyle');
const startTime = Date.now();
try {
// Apply bold and red color to a range
const request = {
documentId,
requests: [{
updateTextStyle: {
range: {
startIndex: 1,
endIndex: 20,
},
textStyle: {
bold: true,
foregroundColor: {
color: {
rgbColor: { red: 1, green: 0, blue: 0 },
},
},
},
fields: 'bold,foregroundColor',
},
}],
};
const response = await docs.documents.batchUpdate(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'applyTextStyle',
status: 'PASS',
duration,
request,
response: {
documentId: response.data.documentId,
},
});
logSuccess(`Text style applied (${duration}ms)`);
log(` Style: bold + red color`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'applyTextStyle',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testApplyParagraphStyle(docs: any, documentId: string) {
logTest('applyParagraphStyle');
const startTime = Date.now();
try {
// Apply center alignment to a paragraph
const request = {
documentId,
requests: [{
updateParagraphStyle: {
range: {
startIndex: 1,
endIndex: 50,
},
paragraphStyle: {
alignment: 'CENTER',
},
fields: 'alignment',
},
}],
};
const response = await docs.documents.batchUpdate(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'applyParagraphStyle',
status: 'PASS',
duration,
request,
response: {
documentId: response.data.documentId,
},
});
logSuccess(`Paragraph style applied (${duration}ms)`);
log(` Style: center alignment`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'applyParagraphStyle',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testInsertTable(docs: any, documentId: string) {
logTest('insertTable');
const startTime = Date.now();
try {
// Get end index
const doc = await docs.documents.get({ documentId, fields: 'body' });
const endIndex = doc.data.body.content[doc.data.body.content.length - 1].endIndex - 1;
const request = {
documentId,
requests: [{
insertTable: {
location: { index: endIndex },
rows: 3,
columns: 3,
},
}],
};
const response = await docs.documents.batchUpdate(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'insertTable',
status: 'PASS',
duration,
request,
response: {
documentId: response.data.documentId,
},
});
logSuccess(`Table inserted (${duration}ms)`);
log(` Size: 3x3`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'insertTable',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testInsertPageBreak(docs: any, documentId: string) {
logTest('insertPageBreak');
const startTime = Date.now();
try {
// Get end index
const doc = await docs.documents.get({ documentId, fields: 'body' });
const endIndex = doc.data.body.content[doc.data.body.content.length - 1].endIndex - 1;
const request = {
documentId,
requests: [{
insertPageBreak: {
location: { index: endIndex },
},
}],
};
const response = await docs.documents.batchUpdate(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'insertPageBreak',
status: 'PASS',
duration,
request,
response: {
documentId: response.data.documentId,
},
});
logSuccess(`Page break inserted (${duration}ms)`);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'insertPageBreak',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testListDocumentTabs(docs: any, documentId: string) {
logTest('listDocumentTabs');
const startTime = Date.now();
try {
const request = { documentId, fields: 'tabs' };
const response = await docs.documents.get(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'listDocumentTabs',
status: 'PASS',
duration,
request,
response: {
tabCount: response.data.tabs?.length || 0,
},
});
logSuccess(`Tabs listed (${duration}ms)`);
log(` Tab count: ${response.data.tabs?.length || 0}`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'listDocumentTabs',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testListDocuments(drive: any) {
logTest('listGoogleDocs');
const startTime = Date.now();
try {
const request = {
q: "mimeType='application/vnd.google-apps.document'",
fields: 'files(id,name,createdTime,modifiedTime)',
pageSize: 10,
};
const response = await drive.files.list(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'listGoogleDocs',
status: 'PASS',
duration,
request,
response: {
documentCount: response.data.files?.length || 0,
},
});
logSuccess(`Documents listed (${duration}ms)`);
log(` Document count: ${response.data.files?.length || 0}`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'listGoogleDocs',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testGetDocumentInfo(drive: any, documentId: string) {
logTest('getDocumentInfo');
const startTime = Date.now();
try {
const request = {
fileId: documentId,
fields: 'id,name,mimeType,createdTime,modifiedTime,owners,permissions',
};
const response = await drive.files.get(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'getDocumentInfo',
status: 'PASS',
duration,
request,
response: {
name: response.data.name,
createdTime: response.data.createdTime,
},
});
logSuccess(`Document info retrieved (${duration}ms)`);
log(` Name: ${response.data.name}`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'getDocumentInfo',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
async function testCreateDocument(drive: any) {
logTest('createDocument');
const startTime = Date.now();
try {
const request = {
requestBody: {
name: `Test Doc ${Date.now()}`,
mimeType: 'application/vnd.google-apps.document',
},
fields: 'id,name',
};
const response = await drive.files.create(request);
const duration = Date.now() - startTime;
testResults.push({
tool: 'createDocument',
status: 'PASS',
duration,
request,
response: {
id: response.data.id,
name: response.data.name,
},
});
logSuccess(`Document created (${duration}ms)`);
log(` ID: ${response.data.id}`, colors.cyan);
log(` Name: ${response.data.name}`, colors.cyan);
// Clean up - delete the created document
await drive.files.delete({ fileId: response.data.id });
log(` Cleaned up test document`, colors.cyan);
} catch (error: any) {
const duration = Date.now() - startTime;
testResults.push({
tool: 'createDocument',
status: 'FAIL',
duration,
error: error.message,
});
logError(`Failed: ${error.message}`);
}
}
function printSummary(startTime: number, testDocumentId: string) {
const totalDuration = Date.now() - startTime;
logSection('Test Summary');
const passedTests = testResults.filter(r => r.status === 'PASS');
const failedTests = testResults.filter(r => r.status === 'FAIL');
const skippedTests = testResults.filter(r => r.status === 'SKIP');
log(`Total Tests: ${testResults.length}`, colors.bright);
logSuccess(`Passed: ${passedTests.length}`);
if (failedTests.length > 0) {
logError(`Failed: ${failedTests.length}`);
}
if (skippedTests.length > 0) {
logWarning(`Skipped: ${skippedTests.length}`);
}
log(`\nTotal Duration: ${totalDuration}ms`, colors.cyan);
log(`\nTest Document: https://docs.google.com/document/d/${testDocumentId}/edit`, colors.cyan);
if (failedTests.length > 0) {
logSection('Failed Tests');
failedTests.forEach(test => {
logError(`${test.tool}: ${test.error}`);
});
}
// Save results to JSON file for the PLATFORM_INTEGRATION_REPORT
const resultsPath = path.join(__dirname, 'test-results.json');
fs.writeFileSync(resultsPath, JSON.stringify(testResults, null, 2));
log(`\nš Results saved to: ${resultsPath}`, colors.cyan);
// Exit with appropriate code
process.exit(failedTests.length > 0 ? 1 : 0);
}
// Run tests
runTests().catch(error => {
logError(`Fatal error: ${error.message}`);
console.error(error);
process.exit(1);
});