import { SentinelMCPServer } from '../server';
import fetch from 'node-fetch';
// Integration tests - these test the MCP protocol end-to-end
describe('Sentinel MCP Integration', () => {
let server: SentinelMCPServer;
let serverUrl: string;
beforeAll(async () => {
// Set test environment
process.env.SENTINEL_PORT = '8788';
process.env.GODOT_PROJECT_ROOT = '/tmp/test-project';
server = new SentinelMCPServer();
await server.start();
serverUrl = 'http://localhost:8788';
});
afterAll(async () => {
// Clean up server
if (server) {
// Server cleanup would go here
}
});
describe('MCP Protocol', () => {
it('should respond to health check', async () => {
const response = await fetch(`${serverUrl}/health`);
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveProperty('status', 'ok');
expect(data).toHaveProperty('timestamp');
});
it('should list available tools', async () => {
const mcpRequest = {
method: 'tools/list',
id: 'test-1'
};
const response = await fetch(`${serverUrl}/mcp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(mcpRequest)
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveProperty('id', 'test-1');
expect(data).toHaveProperty('result');
expect(data.result).toHaveProperty('tools');
expect(Array.isArray(data.result.tools)).toBe(true);
// Check for expected tools
const toolNames = data.result.tools.map((tool: any) => tool.name);
expect(toolNames).toContain('run_tests');
expect(toolNames).toContain('get_context');
expect(toolNames).toContain('apply_patch');
});
it('should handle tool calls with proper schema validation', async () => {
const mcpRequest = {
method: 'tools/call',
params: {
name: 'get_context',
arguments: {
file: 'res://scripts/test.gd',
line: 10,
radius: 5
}
},
id: 'test-2'
};
const response = await fetch(`${serverUrl}/mcp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(mcpRequest)
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveProperty('id', 'test-2');
// Result will depend on whether the file exists, but should not error
expect(data).toHaveProperty('result');
});
it('should handle invalid method gracefully', async () => {
const mcpRequest = {
method: 'invalid_method',
id: 'test-3'
};
const response = await fetch(`${serverUrl}/mcp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(mcpRequest)
});
const data = await response.json();
expect(response.status).toBe(200); // MCP errors are still HTTP 200
expect(data).toHaveProperty('id', 'test-3');
expect(data).toHaveProperty('error');
expect(data.error).toHaveProperty('code');
expect(data.error).toHaveProperty('message');
});
it('should handle malformed JSON requests', async () => {
const response = await fetch(`${serverUrl}/mcp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: 'invalid json'
});
expect(response.status).toBe(500);
});
});
describe('Event System', () => {
it('should accept events from Godot emitter', async () => {
const eventData = {
type: 'script_error',
file: 'res://scripts/test.gd',
line: 42,
message: 'Test error message',
timestamp: new Date().toISOString()
};
const response = await fetch(`${serverUrl}/events`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(eventData)
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveProperty('received', true);
});
it('should reject invalid event format', async () => {
const invalidEvent = {
invalid: 'data'
};
const response = await fetch(`${serverUrl}/events`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(invalidEvent)
});
expect(response.status).toBe(400);
});
});
});
describe('CLI Integration', () => {
// These tests would require mocking the CLI commands
// or setting up a test Godot project
it.skip('should run sentinel test command', async () => {
// This would test the actual CLI functionality
// Skipped because it requires a real Godot project
});
it.skip('should handle file watching', async () => {
// Test the watch command functionality
// Skipped because it requires file system setup
});
});
describe('Error Parsing Integration', () => {
it('should handle real Godot error patterns', () => {
// Test with actual Godot 4 error messages
const realErrors = [
'ERROR: Invalid call. Nonexistent function \'get_damage\' in base \'Node\'.\nAt: res://scripts/combat/fighter.gd:45 @ _ready()',
'SCRIPT ERROR: Parse Error at line 67: Expected \')\' after expression\nPath: res://scripts/combat/state_machine.gd',
'SCRIPT ERROR: Invalid get index \'health\' (on base: \'null instance\')\nAt: res://scripts/combat/fighter.gd:89 @ take_damage()'
];
// Import parser here to avoid circular dependencies in tests
const { ParserGodot4 } = require('../adapters/parser_godot4');
const parser = new ParserGodot4();
realErrors.forEach(errorText => {
const parsed = parser.parseFirstError(errorText);
expect(parsed).not.toBeNull();
expect(parsed).toHaveProperty('file');
expect(parsed).toHaveProperty('line');
expect(parsed).toHaveProperty('message');
expect(typeof parsed?.line).toBe('number');
});
});
});