import { expect } from 'chai';
import { describe, it, before, after, beforeEach, afterEach } from 'mocha';
import { InProcessTestClient, InProcessAuthHelper, InProcessGASTestHelper } from '../../helpers/inProcessClient.js';
import { globalAuthState } from '../../setup/globalAuth.js';
describe('Real exec Integration Tests', () => {
let client: InProcessTestClient;
let auth: InProcessAuthHelper;
let gas: InProcessGASTestHelper;
let testProjects: string[] = [];
before(function() {
// Use the shared global client to avoid multiple server processes
if (!globalAuthState.client || !globalAuthState.auth) {
this.skip(); // Skip if global client not available
}
client = globalAuthState.client!;
auth = globalAuthState.auth!; // Reuse global auth with sessionId
gas = globalAuthState.gas!;
console.log('๐ Using shared global MCP client for real exec integration tests');
});
beforeEach(async function() {
// Skip individual tests if not authenticated
const authStatus = await auth.getAuthStatus();
if (!authStatus.authenticated) {
console.log('โ ๏ธ Authentication required for real exec integration - testing infrastructure only');
// Don't skip, but test infrastructure instead
}
});
afterEach(async function() {
// Cleanup any projects created during tests
for (const projectId of testProjects) {
try {
await gas.cleanupTestProject(projectId);
} catch (error) {
console.warn(`Failed to cleanup project ${projectId}:`, error);
}
}
testProjects = [];
});
after(async () => {
console.log('๐งน Real exec integration tests completed');
});
describe('Real Script Execution with exec', () => {
it('should execute real Google Apps Script code with exec', async function() {
this.timeout(120000); // 2 minutes for real operations
console.log('\n๐ฏ Testing Real exec Execution');
// Verify authentication first
const authStatus = await auth.getAuthStatus();
if (!authStatus.authenticated) {
console.log('โ ๏ธ No authentication - testing tool availability only');
const tools = await client.listTools();
const gasRunTool = tools.find(tool => tool.name === 'exec');
expect(gasRunTool).to.exist;
console.log('โ
exec tool available');
// Test error handling without auth
try {
await client.callTool('exec', {
scriptId: 'test-script-id',
js_statement: '"hello"'
});
expect.fail('Should require authentication');
} catch (error: any) {
const hasAuthError = (error.data && error.data.requiresAuth) ||
(error.message && error.message.includes('auth'));
expect(hasAuthError || error.message.includes('Authentication')).to.be.true;
console.log('โ
Properly requires authentication');
}
console.log('โ
exec infrastructure test completed');
return;
}
// STEP 1: Create real test project
console.log('\n๐ STEP 1: Create Real Test Project');
const projectName = `Real exec Test ${Date.now()}`;
try {
const project = await gas.createTestProject(projectName);
testProjects.push(project.scriptId);
console.log(`โ
Created real project: ${project.scriptId}`);
// STEP 2: Write comprehensive test functions using Function() trick
console.log('\n๐ STEP 2: Write Real Test Functions');
const testCode = `
// Main test functions using the Function() trick for dynamic execution
function testBasicCalculation() {
// Use Function() trick to dynamically execute calculation
return (new Function(\`{
const a = 10;
const b = 20;
return a + b;
}\`))();
}
function testStringManipulation() {
return (new Function(\`{
const text = 'Hello MCP exec';
return {
original: text,
uppercase: text.toUpperCase(),
length: text.length,
words: text.split(' ').length
};
}\`))();
}
function testArrayOperations() {
return (new Function(\`{
const numbers = [1, 2, 3, 4, 5];
return {
original: numbers,
sum: numbers.reduce((a, b) => a + b, 0),
doubled: numbers.map(n => n * 2),
filtered: numbers.filter(n => n > 2)
};
}\`))();
}
function testDateOperations() {
return (new Function(\`{
const now = new Date();
return {
timestamp: now.toISOString(),
year: now.getFullYear(),
month: now.getMonth() + 1,
day: now.getDate(),
formatted: Utilities.formatDate(now, Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm:ss')
};
}\`))();
}
function testComplexLogic() {
return (new Function(\`{
// Complex calculation with multiple steps
let result = 0;
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) {
result += i * 2;
} else {
result += i;
}
}
return {
calculation: result,
steps: 10,
description: 'Sum of numbers 1-10, with even numbers doubled'
};
}\`))();
}
function testErrorHandling() {
try {
return (new Function(\`{
// This will work fine
return { success: true, message: 'Error handling test passed' };
}\`))();
} catch (error) {
return {
success: false,
error: error.toString(),
caught: true
};
}
}
// Los Angeles timezone test (as mentioned in conversation history)
function testLATimezone() {
return (new Function(\`{
const now = new Date();
const laTime = Utilities.formatDate(now, 'America/Los_Angeles', 'yyyy-MM-dd HH:mm:ss z');
return {
utc: now.toISOString(),
losAngeles: laTime,
timezone: 'America/Los_Angeles',
timestamp: now.getTime()
};
}\`))();
}
`;
await gas.writeTestFile(project.scriptId, 'realTests.gs', testCode);
console.log('โ
Written real test functions with Function() trick');
// STEP 3: Test each function with exec
console.log('\n๐ STEP 3: Execute Real Tests with exec');
// Test 1: Basic calculation
console.log('\n๐งฎ Test 1: Basic Calculation');
const calcResult = await client.callAndParse('exec', {
scriptId: project.scriptId,
js_statement: 'testBasicCalculation()',
autoRedeploy: true
});
expect(calcResult.status).to.equal('success');
expect(calcResult.result).to.equal(30); // 10 + 20
console.log('โ
Real basic calculation: SUCCESS');
// Test 2: String manipulation
console.log('\n๐ Test 2: String Manipulation');
const stringResult = await client.callAndParse('exec', {
scriptId: project.scriptId,
js_statement: 'testStringManipulation()',
autoRedeploy: true
});
expect(stringResult.status).to.equal('success');
expect(stringResult.result.uppercase).to.equal('HELLO MCP GAS_RUN');
console.log('โ
Real string manipulation: SUCCESS');
// Test 3: Array operations
console.log('\n๐ข Test 3: Array Operations');
const arrayResult = await client.callAndParse('exec', {
scriptId: project.scriptId,
js_statement: 'testArrayOperations()',
autoRedeploy: true
});
expect(arrayResult.status).to.equal('success');
expect(arrayResult.result.sum).to.equal(15); // 1+2+3+4+5
console.log('โ
Real array operations: SUCCESS');
// Test 4: Date operations with timezone
console.log('\n๐
Test 4: Date Operations with LA Timezone');
const dateResult = await client.callAndParse('exec', {
scriptId: project.scriptId,
js_statement: 'testLATimezone()',
autoRedeploy: true
});
expect(dateResult.status).to.equal('success');
expect(dateResult.result.timezone).to.equal('America/Los_Angeles');
console.log('โ
Real LA timezone handling: SUCCESS');
// Test 5: Complex logic
console.log('\n๐ง Test 5: Complex Logic');
const complexResult = await client.callAndParse('exec', {
scriptId: project.scriptId,
js_statement: 'testComplexLogic()',
autoRedeploy: true
});
expect(complexResult.status).to.equal('success');
expect(complexResult.result.calculation).to.be.a('number');
console.log('โ
Real complex logic: SUCCESS');
// Test 6: Error handling
console.log('\n๐จ Test 6: Error Handling');
const errorResult = await client.callAndParse('exec', {
scriptId: project.scriptId,
js_statement: 'testErrorHandling()',
autoRedeploy: true
});
expect(errorResult.status).to.equal('success');
expect(errorResult.result.success).to.be.true;
console.log('โ
Real error handling: SUCCESS');
console.log('\n๐ All Real exec Integration Tests: PASSED');
} catch (projectError: any) {
console.log('โ ๏ธ Could not create test project:', projectError.message);
// Test infrastructure without real project
const tools = await client.listTools();
const gasRunTool = tools.find(tool => tool.name === 'exec');
expect(gasRunTool).to.exist;
expect(gasRunTool?.inputSchema?.properties?.scriptId).to.exist;
expect(gasRunTool?.inputSchema?.properties?.js_statement).to.exist;
console.log('โ
exec tool schema validated');
console.log('โ
Infrastructure test completed (real project creation failed)');
}
});
it('should handle real error scenarios in exec', async function() {
this.timeout(90000);
console.log('\n๐จ Testing Real Error Scenarios');
const authStatus = await auth.getAuthStatus();
if (!authStatus.authenticated) {
console.log('โ ๏ธ Testing error handling without authentication');
// Test various error scenarios
try {
await client.callTool('exec', {
scriptId: 'invalid-script-id',
js_statement: '"test"'
});
expect.fail('Should fail with invalid script ID');
} catch (error: any) {
expect(error).to.exist;
console.log('โ
Properly handles invalid script ID without auth');
}
return;
}
// Create project for error testing
const projectName = `Error Test ${Date.now()}`;
const project = await gas.createTestProject(projectName);
testProjects.push(project.scriptId);
// Test syntax error handling
console.log('\n๐ง Testing Syntax Error Handling');
const syntaxErrorCode = `
function testSyntaxError() {
// Intentional syntax error
return (new Function(\`{
const broken = {
property: 'missing closing brace'
// missing }
}\`))();
}`;
await gas.writeTestFile(project.scriptId, 'errorTest.gs', syntaxErrorCode);
try {
const errorResult = await client.callAndParse('exec', {
scriptId: project.scriptId,
js_statement: 'testSyntaxError()',
autoRedeploy: true
});
// Should either handle the error gracefully or report it properly
if (errorResult.status === 'success') {
console.log('โ
Error handled gracefully in GAS');
} else {
expect(['error', 'failure']).to.include(errorResult.status);
console.log('โ
Error properly reported');
}
} catch (error) {
console.log('โ
Error properly caught and handled');
}
console.log('โ
Real error scenario testing: COMPLETED');
});
});
describe('Real exec Advanced Features', () => {
it('should handle real autoRedeploy functionality', async function() {
this.timeout(120000);
console.log('\n๐ Testing Real autoRedeploy Feature');
const authStatus = await auth.getAuthStatus();
if (!authStatus.authenticated) {
console.log('โ ๏ธ Testing autoRedeploy schema without authentication');
const tools = await client.listTools();
const gasRunTool = tools.find(tool => tool.name === 'exec');
expect(gasRunTool?.inputSchema?.properties?.autoRedeploy).to.exist;
console.log('โ
autoRedeploy parameter available in schema');
return;
}
// Create project for autoRedeploy testing
const projectName = `AutoRedeploy Test ${Date.now()}`;
const project = await gas.createTestProject(projectName);
testProjects.push(project.scriptId);
// Initial code
const initialCode = `
function dynamicTest() {
return (new Function(\`{
return {
version: 1,
message: 'Initial version',
timestamp: new Date().toISOString()
};
}\`))();
}`;
await gas.writeTestFile(project.scriptId, 'autoRedeployTest.gs', initialCode);
// Test 1: Initial execution
const result1 = await client.callAndParse('exec', {
scriptId: project.scriptId,
js_statement: 'dynamicTest()',
autoRedeploy: true
});
expect(result1.status).to.equal('success');
expect(result1.result.version).to.equal(1);
console.log('โ
Initial execution with autoRedeploy: SUCCESS');
// Update code
const updatedCode = `
function dynamicTest() {
return (new Function(\`{
return {
version: 2,
message: 'Updated version with autoRedeploy',
timestamp: new Date().toISOString(),
autoRedeployed: true
};
}\`))();
}`;
await gas.writeTestFile(project.scriptId, 'autoRedeployTest.gs', updatedCode);
// Test 2: Should pick up changes with autoRedeploy
const result2 = await client.callAndParse('exec', {
scriptId: project.scriptId,
js_statement: 'dynamicTest()',
autoRedeploy: true
});
expect(result2.status).to.equal('success');
expect(result2.result.version).to.equal(2);
console.log('โ
AutoRedeploy picked up changes: SUCCESS');
console.log('โ
Real autoRedeploy functionality: VALIDATED');
});
});
});