634 lines
24 KiB
JavaScript
634 lines
24 KiB
JavaScript
const { test, describe, before } = require('node:test')
|
|
const assert = require('assert/strict')
|
|
const axios = require('axios')
|
|
|
|
const serverUrl = 'http://127.0.0.1:30837/mcp'
|
|
|
|
// Helper function to make HTTP requests
|
|
async function makeHttpRequest (method, urlStr, data = null, headers = {}) {
|
|
try {
|
|
const response = await axios({
|
|
method: method.toUpperCase(),
|
|
url: urlStr,
|
|
data,
|
|
headers
|
|
})
|
|
return {
|
|
status: response.status,
|
|
headers: response.headers,
|
|
data: response.data
|
|
}
|
|
} catch (error) {
|
|
if (error.response) {
|
|
const err = new Error(`Request failed with status ${error.response.status}`)
|
|
err.response = {
|
|
status: error.response.status,
|
|
headers: error.response.headers,
|
|
data: error.response.data
|
|
}
|
|
throw err
|
|
} else {
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper function to initialize MCP session
|
|
async function initSession () {
|
|
const initializeRequest = {
|
|
jsonrpc: '2.0',
|
|
id: 1,
|
|
method: 'initialize',
|
|
params: {
|
|
protocolVersion: '2024-11-05',
|
|
capabilities: {},
|
|
clientInfo: {
|
|
name: 'electerm-test-client',
|
|
version: '1.0.0'
|
|
}
|
|
}
|
|
}
|
|
|
|
const response = await makeHttpRequest('post', serverUrl, initializeRequest, {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json, text/event-stream'
|
|
})
|
|
|
|
const sid = response.headers['mcp-session-id']
|
|
assert.ok(sid && sid !== 'null', `initSession: expected a real session ID but got: ${sid}`)
|
|
return sid
|
|
}
|
|
|
|
// Helper: call a tool and parse the SSE response into JSON result/error
|
|
async function callTool (sid, id, toolName, args) {
|
|
const response = await makeHttpRequest('post', serverUrl, {
|
|
jsonrpc: '2.0',
|
|
id,
|
|
method: 'tools/call',
|
|
params: { name: toolName, arguments: args }
|
|
}, {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json, text/event-stream',
|
|
'mcp-session-id': sid
|
|
})
|
|
assert.equal(response.status, 200)
|
|
const dataLine = response.data.split('\n').find(l => l.startsWith('data: '))
|
|
assert.ok(dataLine, `No data line in SSE response for ${toolName}`)
|
|
const jsonData = JSON.parse(dataLine.substring(6))
|
|
assert.equal(jsonData.jsonrpc, '2.0')
|
|
assert.equal(jsonData.id, id)
|
|
return jsonData
|
|
}
|
|
|
|
// Helper: check if renderer is available by trying list_electerm_tabs
|
|
async function checkRenderer (sid) {
|
|
const jsonData = await callTool(sid, 999, 'list_electerm_tabs', {})
|
|
return !jsonData.error
|
|
}
|
|
|
|
describe('mcp-widget', function () {
|
|
let sessionId = null
|
|
let hasRenderer = false
|
|
|
|
before(async () => {
|
|
sessionId = await initSession()
|
|
hasRenderer = await checkRenderer(sessionId)
|
|
if (!hasRenderer) {
|
|
console.log('No renderer detected — renderer-dependent tests will be skipped')
|
|
}
|
|
})
|
|
|
|
// ─── Protocol-level tests (no renderer needed) ──────────────────────────
|
|
|
|
test('should be accessible at http://127.0.0.1:30837/mcp with default settings', { timeout: 100000 }, async function () {
|
|
const optionsResponse = await makeHttpRequest('options', serverUrl)
|
|
assert.equal(optionsResponse.status, 204)
|
|
assert.equal(optionsResponse.headers['access-control-allow-origin'], undefined)
|
|
assert.ok(optionsResponse.headers['access-control-allow-methods'].includes('POST'))
|
|
assert.ok(optionsResponse.headers['access-control-allow-methods'].includes('GET'))
|
|
assert.ok(optionsResponse.headers['access-control-allow-methods'].includes('DELETE'))
|
|
assert.ok(optionsResponse.headers['access-control-allow-headers'].includes('mcp-session-id'))
|
|
})
|
|
|
|
test('should respond to MCP initialize request', { timeout: 100000 }, async function () {
|
|
const initializeRequest = {
|
|
jsonrpc: '2.0',
|
|
id: 1,
|
|
method: 'initialize',
|
|
params: {
|
|
protocolVersion: '2024-11-05',
|
|
capabilities: {},
|
|
clientInfo: {
|
|
name: 'electerm-test-client',
|
|
version: '1.0.0'
|
|
}
|
|
}
|
|
}
|
|
|
|
const response = await makeHttpRequest('post', serverUrl, initializeRequest, {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json, text/event-stream'
|
|
})
|
|
|
|
assert.equal(response.status, 200)
|
|
assert.equal(response.headers['content-type'], 'text/event-stream')
|
|
const returnedSessionId = response.headers['mcp-session-id']
|
|
assert.ok(returnedSessionId, 'mcp-session-id header is missing')
|
|
assert.notEqual(returnedSessionId, 'null', 'mcp-session-id must not be the string "null"')
|
|
assert.match(returnedSessionId, /^[\w-]+$/, 'mcp-session-id must be a valid identifier')
|
|
|
|
// Parse SSE response
|
|
const sseData = response.data
|
|
assert.ok(sseData.includes('event: message'))
|
|
assert.ok(sseData.includes('data: '))
|
|
|
|
const dataLine = sseData.split('\n').find(line => line.startsWith('data: '))
|
|
assert.ok(dataLine)
|
|
const jsonData = JSON.parse(dataLine.substring(6))
|
|
|
|
assert.equal(jsonData.jsonrpc, '2.0')
|
|
assert.equal(jsonData.id, 1)
|
|
assert.ok(jsonData.result)
|
|
assert.equal(jsonData.result.protocolVersion, '2024-11-05')
|
|
assert.ok(jsonData.result.serverInfo)
|
|
assert.equal(jsonData.result.serverInfo.name, 'electerm-mcp-server')
|
|
})
|
|
|
|
test('should list available tools', { timeout: 100000 }, async function () {
|
|
assert.ok(sessionId)
|
|
|
|
const toolsRequest = {
|
|
jsonrpc: '2.0',
|
|
id: 2,
|
|
method: 'tools/list',
|
|
params: {}
|
|
}
|
|
|
|
const response = await makeHttpRequest('post', serverUrl, toolsRequest, {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json, text/event-stream',
|
|
'mcp-session-id': sessionId
|
|
})
|
|
|
|
assert.equal(response.status, 200)
|
|
assert.equal(response.headers['content-type'], 'text/event-stream')
|
|
|
|
const sseData = response.data
|
|
assert.ok(sseData.includes('event: message'))
|
|
assert.ok(sseData.includes('data: '))
|
|
|
|
const dataLine = sseData.split('\n').find(line => line.startsWith('data: '))
|
|
assert.ok(dataLine)
|
|
const jsonData = JSON.parse(dataLine.substring(6))
|
|
|
|
assert.equal(jsonData.jsonrpc, '2.0')
|
|
assert.equal(jsonData.id, 2)
|
|
assert.ok(jsonData.result)
|
|
assert.ok(Array.isArray(jsonData.result.tools))
|
|
assert.ok(jsonData.result.tools.length > 0)
|
|
|
|
const toolNames = jsonData.result.tools.map(tool => tool.name)
|
|
assert.ok(toolNames.includes('list_electerm_tabs'))
|
|
assert.ok(toolNames.includes('get_electerm_active_tab'))
|
|
assert.ok(toolNames.includes('send_electerm_terminal_command'))
|
|
assert.ok(toolNames.includes('get_electerm_terminal_status'))
|
|
assert.ok(toolNames.includes('cancel_electerm_terminal_command'))
|
|
assert.ok(toolNames.includes('run_electerm_background_command'))
|
|
assert.ok(toolNames.includes('get_electerm_background_task_status'))
|
|
assert.ok(toolNames.includes('get_electerm_background_task_log'))
|
|
assert.ok(toolNames.includes('cancel_electerm_background_task'))
|
|
})
|
|
|
|
// ─── Tool call protocol tests ───────────────────────────────────────────
|
|
|
|
test('should return error when calling tool without renderer', { timeout: 100000 }, async function () {
|
|
assert.ok(sessionId)
|
|
|
|
const jsonData = await callTool(sessionId, 3, 'open_electerm_local_terminal', {})
|
|
|
|
if (hasRenderer) {
|
|
// With renderer: tool call should succeed
|
|
assert.ok(jsonData.result, 'Expected success result with renderer')
|
|
const result = JSON.parse(jsonData.result.content[0].text)
|
|
assert.equal(result.success, true)
|
|
} else {
|
|
// Without renderer: should get a JSON-RPC error
|
|
assert.ok(jsonData.error, 'Expected error without renderer')
|
|
assert.ok(jsonData.error.code !== undefined, 'Error must have a code')
|
|
assert.ok(jsonData.error.message.length > 0, 'Error must have a message')
|
|
}
|
|
})
|
|
|
|
test('should handle multiple tool calls in sequence', { timeout: 100000 }, async function () {
|
|
assert.ok(sessionId)
|
|
|
|
const toolNames = ['list_electerm_tabs', 'get_electerm_active_tab', 'get_electerm_terminal_selection']
|
|
|
|
for (let i = 0; i < toolNames.length; i++) {
|
|
const jsonData = await callTool(sessionId, 10 + i, toolNames[i], {})
|
|
|
|
// Each call must return a valid JSON-RPC response (result or error)
|
|
assert.ok(
|
|
jsonData.result || jsonData.error,
|
|
`Tool ${toolNames[i]} must return either result or error`
|
|
)
|
|
}
|
|
})
|
|
|
|
// ─── Renderer-dependent terminal tests ──────────────────────────────────
|
|
|
|
test('should open local terminal and run command', { timeout: 100000 }, async function () {
|
|
if (!hasRenderer) return test.skip('No renderer available')
|
|
|
|
// Step 1: Open a local terminal
|
|
const openData = await callTool(sessionId, 20, 'open_electerm_local_terminal', {})
|
|
assert.ok(openData.result, 'open_local_terminal should succeed')
|
|
const openResult = JSON.parse(openData.result.content[0].text)
|
|
assert.equal(openResult.success, true)
|
|
assert.ok(openResult.message.includes('local terminal'))
|
|
|
|
// Wait for terminal to initialize
|
|
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
|
|
// Step 2: Send a command
|
|
const uniqueId = Date.now()
|
|
const testCommand = `echo "MCP_TEST_${uniqueId}"`
|
|
|
|
const cmdData = await callTool(sessionId, 21, 'send_electerm_terminal_command', {
|
|
command: testCommand
|
|
})
|
|
assert.ok(cmdData.result, 'send_terminal_command should succeed')
|
|
const cmdResult = JSON.parse(cmdData.result.content[0].text)
|
|
assert.equal(cmdResult.success, true)
|
|
|
|
// Wait for command to execute
|
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
|
|
// Step 3: Get terminal output and verify command was executed
|
|
const outputData = await callTool(sessionId, 22, 'get_electerm_terminal_output', {
|
|
lines: 20
|
|
})
|
|
assert.ok(outputData.result, 'get_terminal_output should succeed')
|
|
const outputResult = JSON.parse(outputData.result.content[0].text)
|
|
assert.ok(outputResult.output !== undefined)
|
|
assert.ok(outputResult.lineCount > 0)
|
|
assert.ok(
|
|
outputResult.output.includes(`MCP_TEST_${uniqueId}`),
|
|
`Expected command output "MCP_TEST_${uniqueId}" in terminal`
|
|
)
|
|
})
|
|
|
|
test('should get terminal output', { timeout: 100000 }, async function () {
|
|
if (!hasRenderer) return test.skip('No renderer available')
|
|
|
|
const jsonData = await callTool(sessionId, 30, 'get_electerm_terminal_output', {
|
|
lines: 10
|
|
})
|
|
assert.ok(jsonData.result, 'get_terminal_output should succeed')
|
|
const result = JSON.parse(jsonData.result.content[0].text)
|
|
assert.ok(result.output !== undefined)
|
|
assert.ok(result.tabId !== undefined)
|
|
assert.equal(typeof result.lineCount, 'number')
|
|
})
|
|
|
|
test('should create SSH bookmark, connect, run command and get output', { timeout: 100000 }, async function () {
|
|
if (!hasRenderer) return test.skip('No renderer available')
|
|
|
|
const {
|
|
TEST_HOST,
|
|
TEST_PASS,
|
|
TEST_USER,
|
|
TEST_PORT
|
|
} = require('../e2e/common/env')
|
|
|
|
const uniqueId = Date.now()
|
|
const bookmarkTitle = `MCP1_SSH_Test_${uniqueId}`
|
|
|
|
// Step 1: Create SSH bookmark
|
|
const addData = await callTool(sessionId, 40, 'add_electerm_bookmark_ssh', {
|
|
title: bookmarkTitle,
|
|
host: TEST_HOST,
|
|
port: parseInt(TEST_PORT, 10),
|
|
username: TEST_USER,
|
|
password: TEST_PASS
|
|
})
|
|
assert.ok(addData.result, 'add_bookmark should succeed')
|
|
const addResult = JSON.parse(addData.result.content[0].text)
|
|
assert.equal(addResult.success, true)
|
|
assert.ok(addResult.id !== undefined)
|
|
const bookmarkId = addResult.id
|
|
|
|
try {
|
|
// Step 2: Open/connect to the bookmark
|
|
const openData = await callTool(sessionId, 41, 'open_electerm_bookmark', {
|
|
id: bookmarkId
|
|
})
|
|
assert.ok(openData.result, 'open_bookmark should succeed')
|
|
const openResult = JSON.parse(openData.result.content[0].text)
|
|
assert.equal(openResult.success, true)
|
|
|
|
// Wait for SSH connection to establish
|
|
await new Promise(resolve => setTimeout(resolve, 8000))
|
|
|
|
// Step 3: Send a command with retry
|
|
const testMarker = `SSH_MCP_TEST_${uniqueId}`
|
|
const testCommand = `echo "${testMarker}"`
|
|
|
|
let cmdResult = null
|
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
const cmdData = await callTool(sessionId, 42, 'send_electerm_terminal_command', {
|
|
command: testCommand
|
|
})
|
|
if (cmdData.error) {
|
|
if (attempt === 3) {
|
|
assert.fail(`send_command failed after 3 attempts: ${cmdData.error.message}`)
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
continue
|
|
}
|
|
cmdResult = JSON.parse(cmdData.result.content[0].text)
|
|
break
|
|
}
|
|
assert.equal(cmdResult.success, true)
|
|
|
|
// Wait for command to execute
|
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
|
|
// Step 4: Get terminal output and verify command was executed
|
|
const outputData = await callTool(sessionId, 43, 'get_electerm_terminal_output', {
|
|
lines: 30
|
|
})
|
|
assert.ok(outputData.result, 'get_terminal_output should succeed')
|
|
const outputResult = JSON.parse(outputData.result.content[0].text)
|
|
assert.ok(outputResult.output !== undefined)
|
|
assert.ok(outputResult.lineCount > 0)
|
|
assert.ok(
|
|
outputResult.output.includes(testMarker),
|
|
`Expected SSH command output "${testMarker}" in terminal`
|
|
)
|
|
} finally {
|
|
// Step 5: Clean up - delete the test bookmark
|
|
const deleteData = await callTool(sessionId, 44, 'delete_electerm_bookmark', {
|
|
id: bookmarkId
|
|
})
|
|
if (deleteData.result) {
|
|
const deleteResult = JSON.parse(deleteData.result.content[0].text)
|
|
assert.equal(deleteResult.success, true)
|
|
}
|
|
|
|
// Step 6: Verify the bookmark was actually deleted
|
|
const listData = await callTool(sessionId, 45, 'list_electerm_bookmarks', {})
|
|
assert.ok(listData.result, 'list_bookmarks should succeed')
|
|
const bookmarks = JSON.parse(listData.result.content[0].text)
|
|
const deletedBookmark = bookmarks.find(b => b.id === bookmarkId)
|
|
assert.ok(!deletedBookmark, `Bookmark with ID ${bookmarkId} should have been deleted but was found`)
|
|
}
|
|
})
|
|
|
|
test('should create Telnet bookmark', { timeout: 100000 }, async function () {
|
|
if (!hasRenderer) return test.skip('No renderer available')
|
|
|
|
const uniqueId = Date.now()
|
|
const bookmarkTitle = `MCP1_Telnet_Test_${uniqueId}`
|
|
|
|
// Create Telnet bookmark
|
|
const addData = await callTool(sessionId, 50, 'add_electerm_bookmark_telnet', {
|
|
title: bookmarkTitle,
|
|
host: '127.0.0.1',
|
|
port: 23,
|
|
username: 'testuser',
|
|
password: 'testpass'
|
|
})
|
|
assert.ok(addData.result, 'add_bookmark should succeed')
|
|
const addResult = JSON.parse(addData.result.content[0].text)
|
|
assert.equal(addResult.success, true)
|
|
assert.ok(addResult.id !== undefined)
|
|
const bookmarkId = addResult.id
|
|
|
|
// Verify bookmark was created by listing bookmarks
|
|
const listData = await callTool(sessionId, 51, 'list_electerm_bookmarks', {})
|
|
assert.ok(listData.result, 'list_bookmarks should succeed')
|
|
const bookmarks = JSON.parse(listData.result.content[0].text)
|
|
const createdBookmark = bookmarks.find(b => b.id === bookmarkId)
|
|
assert.ok(createdBookmark, `Bookmark with ID ${bookmarkId} not found in list`)
|
|
assert.equal(createdBookmark.title, bookmarkTitle)
|
|
assert.equal(createdBookmark.type, 'telnet')
|
|
})
|
|
|
|
// ─── Direct tab open tests ─────────────────────────────────────────────
|
|
|
|
test('should open SSH tab directly without bookmark, run command and get output', { timeout: 100000 }, async function () {
|
|
if (!hasRenderer) return test.skip('No renderer available')
|
|
|
|
const {
|
|
TEST_HOST,
|
|
TEST_PASS,
|
|
TEST_USER,
|
|
TEST_PORT
|
|
} = require('../e2e/common/env')
|
|
|
|
const uniqueId = Date.now()
|
|
const tabTitle = `MCP_Direct_SSH_${uniqueId}`
|
|
|
|
// Step 1: Open SSH tab directly (no bookmark created)
|
|
const openData = await callTool(sessionId, 70, 'open_electerm_tab_ssh', {
|
|
title: tabTitle,
|
|
host: TEST_HOST,
|
|
port: parseInt(TEST_PORT, 10),
|
|
username: TEST_USER,
|
|
password: TEST_PASS
|
|
})
|
|
assert.ok(openData.result, 'open_tab_ssh should succeed')
|
|
const openResult = JSON.parse(openData.result.content[0].text)
|
|
assert.equal(openResult.success, true)
|
|
assert.ok(openResult.tabId !== undefined)
|
|
assert.equal(openResult.type, 'ssh')
|
|
|
|
// Wait for SSH connection to establish
|
|
await new Promise(resolve => setTimeout(resolve, 8000))
|
|
|
|
try {
|
|
// Step 2: Send a command with retry
|
|
const testMarker = `SSH_DIRECT_TEST_${uniqueId}`
|
|
const testCommand = `echo "${testMarker}"`
|
|
|
|
let cmdResult = null
|
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
const cmdData = await callTool(sessionId, 71, 'send_electerm_terminal_command', {
|
|
command: testCommand
|
|
})
|
|
if (cmdData.error) {
|
|
if (attempt === 3) {
|
|
assert.fail(`send_command failed after 3 attempts: ${cmdData.error.message}`)
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
continue
|
|
}
|
|
cmdResult = JSON.parse(cmdData.result.content[0].text)
|
|
break
|
|
}
|
|
assert.equal(cmdResult.success, true)
|
|
|
|
// Wait for command to execute
|
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
|
|
// Step 3: Get terminal output and verify command was executed
|
|
const outputData = await callTool(sessionId, 72, 'get_electerm_terminal_output', {
|
|
lines: 30
|
|
})
|
|
assert.ok(outputData.result, 'get_terminal_output should succeed')
|
|
const outputResult = JSON.parse(outputData.result.content[0].text)
|
|
assert.ok(outputResult.output !== undefined)
|
|
assert.ok(
|
|
outputResult.output.includes(testMarker),
|
|
`Expected SSH command output "${testMarker}" in terminal`
|
|
)
|
|
|
|
// Step 4: Verify no bookmark was created for this connection
|
|
const listData = await callTool(sessionId, 73, 'list_electerm_bookmarks', {})
|
|
assert.ok(listData.result, 'list_bookmarks should succeed')
|
|
const bookmarks = JSON.parse(listData.result.content[0].text)
|
|
const foundBookmark = bookmarks.find(b => b.title === tabTitle)
|
|
assert.ok(!foundBookmark, `No bookmark should be created for direct tab open, but found: ${JSON.stringify(foundBookmark)}`)
|
|
} finally {
|
|
// Step 5: Close the tab
|
|
const tabId = openResult.tabId
|
|
await callTool(sessionId, 74, 'close_electerm_tab', { tabId })
|
|
}
|
|
})
|
|
|
|
test('should list direct open tools in tools/list', { timeout: 100000 }, async function () {
|
|
assert.ok(sessionId)
|
|
|
|
const toolsRequest = {
|
|
jsonrpc: '2.0',
|
|
id: 75,
|
|
method: 'tools/list',
|
|
params: {}
|
|
}
|
|
|
|
const response = await makeHttpRequest('post', serverUrl, toolsRequest, {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json, text/event-stream',
|
|
'mcp-session-id': sessionId
|
|
})
|
|
|
|
assert.equal(response.status, 200)
|
|
const dataLine = response.data.split('\n').find(l => l.startsWith('data: '))
|
|
const jsonData = JSON.parse(dataLine.substring(6))
|
|
const toolNames = jsonData.result.tools.map(tool => tool.name)
|
|
|
|
assert.ok(toolNames.includes('open_electerm_tab_ssh'), 'Missing open_electerm_tab_ssh')
|
|
assert.ok(toolNames.includes('open_electerm_tab_telnet'), 'Missing open_electerm_tab_telnet')
|
|
assert.ok(toolNames.includes('open_electerm_tab_serial'), 'Missing open_electerm_tab_serial')
|
|
assert.ok(toolNames.includes('open_electerm_tab_local'), 'Missing open_electerm_tab_local')
|
|
})
|
|
|
|
// ─── onData test ────────────────────────────────────────────────────────
|
|
|
|
test('list_tabs should include onData field for each tab', { timeout: 100000 }, async function () {
|
|
if (!hasRenderer) return test.skip('No renderer available')
|
|
|
|
const jsonData = await callTool(sessionId, 60, 'list_electerm_tabs', {})
|
|
assert.ok(jsonData.result, 'list_tabs should succeed')
|
|
|
|
const tabs = JSON.parse(jsonData.result.content[0].text)
|
|
assert.ok(Array.isArray(tabs), 'Expected an array of tabs')
|
|
|
|
for (const tab of tabs) {
|
|
assert.ok('onData' in tab, `Tab ${tab.id} is missing the onData field`)
|
|
assert.equal(typeof tab.onData, 'string', `onData for tab ${tab.id} should be string`)
|
|
}
|
|
})
|
|
|
|
// ─── Terminal idle tests ────────────────────────────────────────────────
|
|
|
|
test('should wait for terminal idle after a quick echo command', { timeout: 100000 }, async function () {
|
|
if (!hasRenderer) return test.skip('No renderer available')
|
|
|
|
const openData = await callTool(sessionId, 61, 'open_electerm_local_terminal', {})
|
|
assert.ok(openData.result, 'open_local_terminal should succeed')
|
|
const openResult = JSON.parse(openData.result.content[0].text)
|
|
assert.equal(openResult.success, true)
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
|
|
const marker = `IDLE_TEST_${Date.now()}`
|
|
|
|
const sendData = await callTool(sessionId, 62, 'send_electerm_terminal_command', {
|
|
command: `echo "${marker}"`
|
|
})
|
|
assert.ok(sendData.result, 'send_command should succeed')
|
|
|
|
const waitData = await callTool(sessionId, 63, 'wait_for_electerm_terminal_idle', {
|
|
timeout: 20000,
|
|
lines: 30
|
|
})
|
|
assert.ok(waitData.result, 'wait_for_terminal_idle should succeed')
|
|
|
|
const waitResult = JSON.parse(waitData.result.content[0].text)
|
|
assert.equal(waitResult.timedOut, false, 'Expected terminal to become idle before timeout')
|
|
assert.ok(waitResult.elapsed >= 0, 'Expected non-negative elapsed time')
|
|
assert.equal(typeof waitResult.lineCount, 'number')
|
|
assert.equal(typeof waitResult.output, 'string')
|
|
assert.ok(
|
|
waitResult.output.includes(marker),
|
|
`Expected echo output "${marker}" in terminal after idle wait`
|
|
)
|
|
})
|
|
|
|
test('should correctly wait for a longer-running command to finish', { timeout: 100000 }, async function () {
|
|
if (!hasRenderer) return test.skip('No renderer available')
|
|
|
|
const openData = await callTool(sessionId, 64, 'open_electerm_local_terminal', {})
|
|
assert.ok(openData.result, 'open_local_terminal should succeed')
|
|
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
|
|
const marker = `SLOW_TEST_${Date.now()}`
|
|
|
|
const sendData = await callTool(sessionId, 65, 'send_electerm_terminal_command', {
|
|
command: `sleep 5 && echo "${marker}"`
|
|
})
|
|
assert.ok(sendData.result, 'send_command should succeed')
|
|
|
|
const waitData = await callTool(sessionId, 66, 'wait_for_electerm_terminal_idle', {
|
|
timeout: 30000,
|
|
lines: 30
|
|
})
|
|
assert.ok(waitData.result, 'wait_for_terminal_idle should succeed')
|
|
|
|
const waitResult = JSON.parse(waitData.result.content[0].text)
|
|
assert.equal(waitResult.timedOut, false, 'Expected terminal to become idle before 30s timeout')
|
|
assert.ok(waitResult.elapsed >= 5000, `Expected at least 5s elapsed but got ${waitResult.elapsed}ms`)
|
|
assert.ok(
|
|
waitResult.output.includes(marker),
|
|
`Expected slow command output "${marker}" after idle wait`
|
|
)
|
|
})
|
|
|
|
test('should return timedOut:true when terminal never becomes idle within timeout', { timeout: 100000 }, async function () {
|
|
if (!hasRenderer) return test.skip('No renderer available')
|
|
|
|
const openData = await callTool(sessionId, 67, 'open_electerm_local_terminal', {})
|
|
assert.ok(openData.result, 'open_local_terminal should succeed')
|
|
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
|
|
const sendData = await callTool(sessionId, 68, 'send_electerm_terminal_command', {
|
|
command: 'ping -c 30 localhost'
|
|
})
|
|
assert.ok(sendData.result, 'send_command should succeed')
|
|
|
|
const waitData = await callTool(sessionId, 69, 'wait_for_electerm_terminal_idle', {
|
|
timeout: 8000,
|
|
lines: 20,
|
|
minWait: 500
|
|
})
|
|
assert.ok(waitData.result, 'wait_for_terminal_idle should succeed')
|
|
|
|
const waitResult = JSON.parse(waitData.result.content[0].text)
|
|
assert.equal(waitResult.timedOut, true, 'Expected timedOut:true when command keeps running')
|
|
assert.ok(waitResult.elapsed >= 8000, `Expected at least 8s elapsed but got ${waitResult.elapsed}ms`)
|
|
assert.equal(typeof waitResult.output, 'string', 'Should still return whatever output is in buffer')
|
|
})
|
|
})
|