mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
chore: remove integration tests (#11598)
* chore: remove integration test files * chore: remove integration test job from CI workflow --------- Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: Hannes Rudolph <hrudolph@gmail.com>
This commit is contained in:
parent
00c35e691f
commit
aff46b14e2
4 changed files with 0 additions and 746 deletions
63
.github/workflows/code-qa.yml
vendored
63
.github/workflows/code-qa.yml
vendored
|
|
@ -58,66 +58,3 @@ jobs:
|
|||
uses: ./.github/actions/setup-node-pnpm
|
||||
- name: Run unit tests
|
||||
run: pnpm test
|
||||
|
||||
check-openrouter-api-key:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
exists: ${{ steps.openrouter-api-key-check.outputs.defined }}
|
||||
steps:
|
||||
- name: Check if OpenRouter API key exists
|
||||
id: openrouter-api-key-check
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ secrets.OPENROUTER_API_KEY }}" != '' ]; then
|
||||
echo "defined=true" >> $GITHUB_OUTPUT;
|
||||
else
|
||||
echo "defined=false" >> $GITHUB_OUTPUT;
|
||||
fi
|
||||
|
||||
integration-test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-openrouter-api-key]
|
||||
if: needs.check-openrouter-api-key.outputs.exists == 'true'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Node.js and pnpm
|
||||
uses: ./.github/actions/setup-node-pnpm
|
||||
- name: Create .env.local file
|
||||
working-directory: apps/vscode-e2e
|
||||
run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.local
|
||||
- name: Set VS Code test version
|
||||
run: echo "VSCODE_VERSION=1.101.2" >> $GITHUB_ENV
|
||||
- name: Cache VS Code test runtime
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/vscode-e2e/.vscode-test
|
||||
key: ${{ runner.os }}-vscode-test-${{ env.VSCODE_VERSION }}
|
||||
- name: Pre-download VS Code test runtime with retry
|
||||
working-directory: apps/vscode-e2e
|
||||
run: |
|
||||
for attempt in 1 2 3; do
|
||||
echo "Download attempt $attempt of 3..."
|
||||
node -e "
|
||||
const { downloadAndUnzipVSCode } = require('@vscode/test-electron');
|
||||
downloadAndUnzipVSCode({ version: process.env.VSCODE_VERSION || '1.101.2' })
|
||||
.then(() => {
|
||||
console.log('✅ VS Code test runtime downloaded successfully');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('❌ Failed to download VS Code (attempt $attempt):', err);
|
||||
process.exit(1);
|
||||
});
|
||||
" && break || {
|
||||
if [ $attempt -eq 3 ]; then
|
||||
echo "All download attempts failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "Retrying in 5 seconds..."
|
||||
sleep 5
|
||||
}
|
||||
done
|
||||
- name: Run integration tests
|
||||
working-directory: apps/vscode-e2e
|
||||
run: xvfb-run -a pnpm test:ci
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
// npx vitest run src/__tests__/CloudService.integration.test.ts
|
||||
|
||||
import type { ExtensionContext } from "vscode"
|
||||
|
||||
import { CloudService } from "../CloudService.js"
|
||||
import { StaticSettingsService } from "../StaticSettingsService.js"
|
||||
import { CloudSettingsService } from "../CloudSettingsService.js"
|
||||
|
||||
vi.mock("vscode", () => ({
|
||||
ExtensionContext: vi.fn(),
|
||||
window: {
|
||||
showInformationMessage: vi.fn(),
|
||||
showErrorMessage: vi.fn(),
|
||||
},
|
||||
env: {
|
||||
openExternal: vi.fn(),
|
||||
},
|
||||
Uri: {
|
||||
parse: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
describe("CloudService Integration - Settings Service Selection", () => {
|
||||
let mockContext: ExtensionContext
|
||||
|
||||
beforeEach(() => {
|
||||
CloudService.resetInstance()
|
||||
|
||||
mockContext = {
|
||||
subscriptions: [],
|
||||
workspaceState: {
|
||||
get: vi.fn(),
|
||||
update: vi.fn(),
|
||||
keys: vi.fn().mockReturnValue([]),
|
||||
},
|
||||
secrets: {
|
||||
get: vi.fn(),
|
||||
store: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
onDidChange: vi.fn().mockReturnValue({ dispose: vi.fn() }),
|
||||
},
|
||||
globalState: {
|
||||
get: vi.fn(),
|
||||
update: vi.fn(),
|
||||
setKeysForSync: vi.fn(),
|
||||
keys: vi.fn().mockReturnValue([]),
|
||||
},
|
||||
extensionUri: { scheme: "file", path: "/mock/path" },
|
||||
extensionPath: "/mock/path",
|
||||
extensionMode: 1,
|
||||
asAbsolutePath: vi.fn((relativePath: string) => `/mock/path/${relativePath}`),
|
||||
storageUri: { scheme: "file", path: "/mock/storage" },
|
||||
extension: {
|
||||
packageJSON: {
|
||||
version: "1.0.0",
|
||||
},
|
||||
},
|
||||
} as unknown as ExtensionContext
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
CloudService.resetInstance()
|
||||
delete process.env.ROO_CODE_CLOUD_ORG_SETTINGS
|
||||
delete process.env.ROO_CODE_CLOUD_TOKEN
|
||||
})
|
||||
|
||||
it("should use CloudSettingsService when no environment variable is set", async () => {
|
||||
// Ensure no environment variables are set
|
||||
delete process.env.ROO_CODE_CLOUD_ORG_SETTINGS
|
||||
delete process.env.ROO_CODE_CLOUD_TOKEN
|
||||
|
||||
const cloudService = await CloudService.createInstance(mockContext)
|
||||
|
||||
// Access the private settingsService to check its type
|
||||
const settingsService = (cloudService as unknown as { settingsService: unknown }).settingsService
|
||||
expect(settingsService).toBeInstanceOf(CloudSettingsService)
|
||||
})
|
||||
|
||||
it("should use StaticSettingsService when ROO_CODE_CLOUD_ORG_SETTINGS is set", async () => {
|
||||
const validSettings = {
|
||||
version: 1,
|
||||
cloudSettings: {
|
||||
recordTaskMessages: true,
|
||||
enableTaskSharing: true,
|
||||
taskShareExpirationDays: 30,
|
||||
},
|
||||
defaultSettings: {
|
||||
enableCheckpoints: true,
|
||||
},
|
||||
allowList: {
|
||||
allowAll: true,
|
||||
providers: {},
|
||||
},
|
||||
}
|
||||
|
||||
// Set the environment variable
|
||||
process.env.ROO_CODE_CLOUD_ORG_SETTINGS = Buffer.from(JSON.stringify(validSettings)).toString("base64")
|
||||
|
||||
const cloudService = await CloudService.createInstance(mockContext)
|
||||
|
||||
// Access the private settingsService to check its type
|
||||
const settingsService = (cloudService as unknown as { settingsService: unknown }).settingsService
|
||||
expect(settingsService).toBeInstanceOf(StaticSettingsService)
|
||||
|
||||
// Verify the settings are correctly loaded
|
||||
expect(cloudService.getAllowList()).toEqual(validSettings.allowList)
|
||||
})
|
||||
|
||||
it("should throw error when ROO_CODE_CLOUD_ORG_SETTINGS contains invalid data", async () => {
|
||||
// Set invalid environment variable
|
||||
process.env.ROO_CODE_CLOUD_ORG_SETTINGS = "invalid-base64-data"
|
||||
|
||||
await expect(CloudService.createInstance(mockContext)).rejects.toThrow("Failed to initialize CloudService")
|
||||
})
|
||||
|
||||
it("should prioritize static token auth when both environment variables are set", async () => {
|
||||
const validSettings = {
|
||||
version: 1,
|
||||
cloudSettings: {
|
||||
recordTaskMessages: true,
|
||||
enableTaskSharing: true,
|
||||
taskShareExpirationDays: 30,
|
||||
},
|
||||
defaultSettings: {
|
||||
enableCheckpoints: true,
|
||||
},
|
||||
allowList: {
|
||||
allowAll: true,
|
||||
providers: {},
|
||||
},
|
||||
}
|
||||
|
||||
// Set both environment variables
|
||||
process.env.ROO_CODE_CLOUD_TOKEN = "test-token"
|
||||
process.env.ROO_CODE_CLOUD_ORG_SETTINGS = Buffer.from(JSON.stringify(validSettings)).toString("base64")
|
||||
|
||||
const cloudService = await CloudService.createInstance(mockContext)
|
||||
|
||||
// Should use StaticSettingsService for settings
|
||||
const settingsService = (cloudService as unknown as { settingsService: unknown }).settingsService
|
||||
expect(settingsService).toBeInstanceOf(StaticSettingsService)
|
||||
|
||||
// Should use StaticTokenAuthService for auth (from the existing logic)
|
||||
expect(cloudService.isAuthenticated()).toBe(true)
|
||||
expect(cloudService.hasActiveSession()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,406 +0,0 @@
|
|||
// Integration tests for command execution timeout functionality
|
||||
// npx vitest run src/core/tools/__tests__/executeCommandTimeout.integration.spec.ts
|
||||
|
||||
import * as vscode from "vscode"
|
||||
import * as fs from "fs/promises"
|
||||
import { executeCommandInTerminal, executeCommandTool, ExecuteCommandOptions } from "../ExecuteCommandTool"
|
||||
import { Task } from "../../task/Task"
|
||||
import { TerminalRegistry } from "../../../integrations/terminal/TerminalRegistry"
|
||||
|
||||
// Mock dependencies
|
||||
vitest.mock("vscode", () => ({
|
||||
workspace: {
|
||||
getConfiguration: vitest.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vitest.mock("fs/promises")
|
||||
vitest.mock("../../../integrations/terminal/TerminalRegistry")
|
||||
vitest.mock("../../task/Task")
|
||||
vitest.mock("../../prompts/responses", () => ({
|
||||
formatResponse: {
|
||||
toolError: vitest.fn((msg) => `Tool Error: ${msg}`),
|
||||
rooIgnoreError: vitest.fn((msg) => `RooIgnore Error: ${msg}`),
|
||||
},
|
||||
}))
|
||||
vitest.mock("../../../utils/text-normalization", () => ({
|
||||
unescapeHtmlEntities: vitest.fn((text) => text),
|
||||
}))
|
||||
vitest.mock("../../../shared/package", () => ({
|
||||
Package: {
|
||||
name: "roo-cline",
|
||||
},
|
||||
}))
|
||||
|
||||
describe("Command Execution Timeout Integration", () => {
|
||||
let mockTask: any
|
||||
let mockTerminal: any
|
||||
let mockProcess: any
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
|
||||
// Mock fs.access to resolve successfully for working directory
|
||||
;(fs.access as any).mockResolvedValue(undefined)
|
||||
|
||||
// Mock task
|
||||
mockTask = {
|
||||
cwd: "/test/directory",
|
||||
terminalProcess: undefined,
|
||||
providerRef: {
|
||||
deref: vitest.fn().mockResolvedValue({
|
||||
postMessageToWebview: vitest.fn(),
|
||||
}),
|
||||
},
|
||||
say: vitest.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
// Mock terminal process
|
||||
mockProcess = {
|
||||
abort: vitest.fn(),
|
||||
then: vitest.fn(),
|
||||
catch: vitest.fn(),
|
||||
}
|
||||
|
||||
// Mock terminal
|
||||
mockTerminal = {
|
||||
runCommand: vitest.fn().mockReturnValue(mockProcess),
|
||||
getCurrentWorkingDirectory: vitest.fn().mockReturnValue("/test/directory"),
|
||||
}
|
||||
|
||||
// Mock TerminalRegistry
|
||||
;(TerminalRegistry.getOrCreateTerminal as any).mockResolvedValue(mockTerminal)
|
||||
|
||||
// Mock VSCode configuration
|
||||
const mockGetConfiguration = vitest.fn().mockReturnValue({
|
||||
get: vitest.fn().mockReturnValue(0), // Default 0 (no timeout)
|
||||
})
|
||||
;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration())
|
||||
})
|
||||
|
||||
it("should pass timeout configuration to executeCommand", async () => {
|
||||
const customTimeoutMs = 15000 // 15 seconds in milliseconds
|
||||
const options: ExecuteCommandOptions = {
|
||||
executionId: "test-execution",
|
||||
command: "echo test",
|
||||
commandExecutionTimeout: customTimeoutMs,
|
||||
}
|
||||
|
||||
// Mock a quick-completing process
|
||||
const quickProcess = Promise.resolve()
|
||||
mockTerminal.runCommand.mockReturnValue(quickProcess)
|
||||
|
||||
await executeCommandInTerminal(mockTask as Task, options)
|
||||
|
||||
// Verify that the terminal was called with the command
|
||||
expect(mockTerminal.runCommand).toHaveBeenCalledWith("echo test", expect.any(Object))
|
||||
})
|
||||
|
||||
it("should handle timeout scenario", async () => {
|
||||
const shortTimeoutMs = 100 // Very short timeout in milliseconds
|
||||
const options: ExecuteCommandOptions = {
|
||||
executionId: "test-execution",
|
||||
command: "sleep 10",
|
||||
commandExecutionTimeout: shortTimeoutMs,
|
||||
}
|
||||
|
||||
// Create a process that never resolves but has an abort method
|
||||
const longRunningProcess = new Promise(() => {
|
||||
// Never resolves to simulate a hanging command
|
||||
})
|
||||
|
||||
// Add abort method to the promise
|
||||
;(longRunningProcess as any).abort = vitest.fn()
|
||||
|
||||
mockTerminal.runCommand.mockReturnValue(longRunningProcess)
|
||||
|
||||
// Execute with timeout
|
||||
const result = await executeCommandInTerminal(mockTask as Task, options)
|
||||
|
||||
// Should return timeout error
|
||||
expect(result[0]).toBe(false) // Not rejected by user
|
||||
expect(result[1]).toContain("terminated after exceeding")
|
||||
expect(result[1]).toContain("0.1s") // Should show seconds in error message
|
||||
}, 10000) // Increase test timeout to 10 seconds
|
||||
|
||||
it("should abort process on timeout", async () => {
|
||||
const shortTimeoutMs = 50 // Short timeout in milliseconds
|
||||
const options: ExecuteCommandOptions = {
|
||||
executionId: "test-execution",
|
||||
command: "sleep 10",
|
||||
commandExecutionTimeout: shortTimeoutMs,
|
||||
}
|
||||
|
||||
// Create a process that can be aborted
|
||||
const abortSpy = vitest.fn()
|
||||
|
||||
// Mock the process to never resolve but be abortable
|
||||
const neverResolvingPromise = new Promise(() => {})
|
||||
;(neverResolvingPromise as any).abort = abortSpy
|
||||
|
||||
mockTerminal.runCommand.mockReturnValue(neverResolvingPromise)
|
||||
|
||||
await executeCommandInTerminal(mockTask as Task, options)
|
||||
|
||||
// Verify abort was called
|
||||
expect(abortSpy).toHaveBeenCalled()
|
||||
}, 5000) // Increase test timeout to 5 seconds
|
||||
|
||||
it("should clean up timeout on successful completion", async () => {
|
||||
const options: ExecuteCommandOptions = {
|
||||
executionId: "test-execution",
|
||||
command: "echo test",
|
||||
commandExecutionTimeout: 5000,
|
||||
}
|
||||
|
||||
// Mock a process that completes quickly
|
||||
const quickProcess = Promise.resolve()
|
||||
mockTerminal.runCommand.mockReturnValue(quickProcess)
|
||||
|
||||
const result = await executeCommandInTerminal(mockTask as Task, options)
|
||||
|
||||
// Should complete successfully without timeout
|
||||
expect(result[0]).toBe(false) // Not rejected
|
||||
expect(result[1]).not.toContain("terminated after exceeding")
|
||||
})
|
||||
|
||||
it("should use default timeout when not specified (0 = no timeout)", async () => {
|
||||
const options: ExecuteCommandOptions = {
|
||||
executionId: "test-execution",
|
||||
command: "echo test",
|
||||
// commandExecutionTimeout not specified, should use default (0)
|
||||
}
|
||||
|
||||
const quickProcess = Promise.resolve()
|
||||
mockTerminal.runCommand.mockReturnValue(quickProcess)
|
||||
|
||||
await executeCommandInTerminal(mockTask as Task, options)
|
||||
|
||||
// Should complete without issues using default (no timeout)
|
||||
expect(mockTerminal.runCommand).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should not timeout when commandExecutionTimeout is 0", async () => {
|
||||
const options: ExecuteCommandOptions = {
|
||||
executionId: "test-execution",
|
||||
command: "sleep 10",
|
||||
commandExecutionTimeout: 0, // No timeout
|
||||
}
|
||||
|
||||
// Create a process that resolves after a delay to simulate a long-running command
|
||||
const longRunningProcess = new Promise((resolve) => {
|
||||
setTimeout(resolve, 200) // 200ms delay
|
||||
})
|
||||
|
||||
mockTerminal.runCommand.mockReturnValue(longRunningProcess)
|
||||
|
||||
const result = await executeCommandInTerminal(mockTask as Task, options)
|
||||
|
||||
// Should complete successfully without timeout
|
||||
expect(result[0]).toBe(false) // Not rejected
|
||||
expect(result[1]).not.toContain("terminated after exceeding")
|
||||
})
|
||||
|
||||
describe("Command Timeout Allowlist", () => {
|
||||
let mockBlock: any
|
||||
let mockAskApproval: any
|
||||
let mockHandleError: any
|
||||
let mockPushToolResult: any
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset mocks for allowlist tests
|
||||
vitest.clearAllMocks()
|
||||
;(fs.access as any).mockResolvedValue(undefined)
|
||||
;(TerminalRegistry.getOrCreateTerminal as any).mockResolvedValue(mockTerminal)
|
||||
|
||||
// Mock the executeCommandTool parameters
|
||||
mockBlock = {
|
||||
type: "tool_use",
|
||||
name: "execute_command",
|
||||
params: {
|
||||
command: "",
|
||||
cwd: undefined,
|
||||
},
|
||||
nativeArgs: {
|
||||
command: "",
|
||||
cwd: undefined,
|
||||
},
|
||||
partial: false,
|
||||
}
|
||||
|
||||
mockAskApproval = vitest.fn().mockResolvedValue(true) // Always approve
|
||||
mockHandleError = vitest.fn()
|
||||
mockPushToolResult = vitest.fn()
|
||||
|
||||
// Mock task with additional properties needed by executeCommandTool
|
||||
mockTask = {
|
||||
cwd: "/test/directory",
|
||||
terminalProcess: undefined,
|
||||
providerRef: {
|
||||
deref: vitest.fn().mockResolvedValue({
|
||||
postMessageToWebview: vitest.fn(),
|
||||
getState: vitest.fn().mockResolvedValue({
|
||||
terminalOutputLineLimit: 500,
|
||||
terminalShellIntegrationDisabled: false,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
say: vitest.fn().mockResolvedValue(undefined),
|
||||
consecutiveMistakeCount: 0,
|
||||
recordToolError: vitest.fn(),
|
||||
sayAndCreateMissingParamError: vitest.fn(),
|
||||
rooIgnoreController: {
|
||||
validateCommand: vitest.fn().mockReturnValue(null),
|
||||
},
|
||||
lastMessageTs: Date.now(),
|
||||
ask: vitest.fn(),
|
||||
didRejectTool: false,
|
||||
}
|
||||
})
|
||||
|
||||
it("should skip timeout for commands in allowlist", async () => {
|
||||
// Mock VSCode configuration with timeout and allowlist
|
||||
const mockGetConfiguration = vitest.fn().mockReturnValue({
|
||||
get: vitest.fn().mockImplementation((key: string) => {
|
||||
if (key === "commandExecutionTimeout") return 1 // 1 second timeout
|
||||
if (key === "commandTimeoutAllowlist") return ["npm", "git"]
|
||||
return undefined
|
||||
}),
|
||||
})
|
||||
;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration())
|
||||
|
||||
mockBlock.params.command = "npm install"
|
||||
mockBlock.nativeArgs.command = "npm install"
|
||||
|
||||
// Create a process that would timeout if not allowlisted
|
||||
const longRunningProcess = new Promise((resolve) => {
|
||||
setTimeout(resolve, 2000) // 2 seconds, longer than 1 second timeout
|
||||
})
|
||||
mockTerminal.runCommand.mockReturnValue(longRunningProcess)
|
||||
|
||||
await executeCommandTool.handle(mockTask as Task, mockBlock, {
|
||||
askApproval: mockAskApproval,
|
||||
handleError: mockHandleError,
|
||||
pushToolResult: mockPushToolResult,
|
||||
})
|
||||
|
||||
// Should complete successfully without timeout because "npm" is in allowlist
|
||||
expect(mockPushToolResult).toHaveBeenCalled()
|
||||
const result = mockPushToolResult.mock.calls[0][0]
|
||||
expect(result).not.toContain("terminated after exceeding")
|
||||
}, 3000)
|
||||
|
||||
it("should apply timeout for commands not in allowlist", async () => {
|
||||
// Mock VSCode configuration with timeout and allowlist
|
||||
const mockGetConfiguration = vitest.fn().mockReturnValue({
|
||||
get: vitest.fn().mockImplementation((key: string) => {
|
||||
if (key === "commandExecutionTimeout") return 1 // 1 second timeout
|
||||
if (key === "commandTimeoutAllowlist") return ["npm", "git"]
|
||||
return undefined
|
||||
}),
|
||||
})
|
||||
;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration())
|
||||
|
||||
mockBlock.params.command = "sleep 10" // Not in allowlist
|
||||
mockBlock.nativeArgs.command = "sleep 10"
|
||||
|
||||
// Create a process that never resolves
|
||||
const neverResolvingProcess = new Promise(() => {})
|
||||
;(neverResolvingProcess as any).abort = vitest.fn()
|
||||
mockTerminal.runCommand.mockReturnValue(neverResolvingProcess)
|
||||
|
||||
await executeCommandTool.handle(mockTask as Task, mockBlock, {
|
||||
askApproval: mockAskApproval,
|
||||
handleError: mockHandleError,
|
||||
pushToolResult: mockPushToolResult,
|
||||
})
|
||||
|
||||
// Should timeout because "sleep" is not in allowlist
|
||||
expect(mockPushToolResult).toHaveBeenCalled()
|
||||
const result = mockPushToolResult.mock.calls[0][0]
|
||||
expect(result).toContain("terminated after exceeding")
|
||||
}, 3000)
|
||||
|
||||
it("should handle empty allowlist", async () => {
|
||||
// Mock VSCode configuration with timeout and empty allowlist
|
||||
const mockGetConfiguration = vitest.fn().mockReturnValue({
|
||||
get: vitest.fn().mockImplementation((key: string) => {
|
||||
if (key === "commandExecutionTimeout") return 1 // 1 second timeout
|
||||
if (key === "commandTimeoutAllowlist") return []
|
||||
return undefined
|
||||
}),
|
||||
})
|
||||
;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration())
|
||||
|
||||
mockBlock.params.command = "npm install"
|
||||
mockBlock.nativeArgs.command = "npm install"
|
||||
|
||||
// Create a process that never resolves
|
||||
const neverResolvingProcess = new Promise(() => {})
|
||||
;(neverResolvingProcess as any).abort = vitest.fn()
|
||||
mockTerminal.runCommand.mockReturnValue(neverResolvingProcess)
|
||||
|
||||
await executeCommandTool.handle(mockTask as Task, mockBlock, {
|
||||
askApproval: mockAskApproval,
|
||||
handleError: mockHandleError,
|
||||
pushToolResult: mockPushToolResult,
|
||||
})
|
||||
|
||||
// Should timeout because allowlist is empty
|
||||
expect(mockPushToolResult).toHaveBeenCalled()
|
||||
const result = mockPushToolResult.mock.calls[0][0]
|
||||
expect(result).toContain("terminated after exceeding")
|
||||
}, 3000)
|
||||
|
||||
it("should match command prefixes correctly", async () => {
|
||||
// Mock VSCode configuration with timeout and allowlist
|
||||
const mockGetConfiguration = vitest.fn().mockReturnValue({
|
||||
get: vitest.fn().mockImplementation((key: string) => {
|
||||
if (key === "commandExecutionTimeout") return 1 // 1 second timeout
|
||||
if (key === "commandTimeoutAllowlist") return ["git log", "npm run"]
|
||||
return undefined
|
||||
}),
|
||||
})
|
||||
;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration())
|
||||
|
||||
const longRunningProcess = new Promise((resolve) => {
|
||||
setTimeout(resolve, 2000) // 2 seconds
|
||||
})
|
||||
const neverResolvingProcess = new Promise(() => {})
|
||||
;(neverResolvingProcess as any).abort = vitest.fn()
|
||||
|
||||
// Test exact prefix match - should not timeout
|
||||
mockBlock.params.command = "git log --oneline"
|
||||
mockBlock.nativeArgs.command = "git log --oneline"
|
||||
mockTerminal.runCommand.mockReturnValueOnce(longRunningProcess)
|
||||
|
||||
await executeCommandTool.handle(mockTask as Task, mockBlock, {
|
||||
askApproval: mockAskApproval,
|
||||
handleError: mockHandleError,
|
||||
pushToolResult: mockPushToolResult,
|
||||
})
|
||||
|
||||
expect(mockPushToolResult).toHaveBeenCalled()
|
||||
const result1 = mockPushToolResult.mock.calls[0][0]
|
||||
expect(result1).not.toContain("terminated after exceeding")
|
||||
|
||||
// Reset mocks for second test
|
||||
mockPushToolResult.mockClear()
|
||||
|
||||
// Test partial prefix match (should not match) - should timeout
|
||||
mockBlock.params.command = "git status" // "git" alone is not in allowlist, only "git log"
|
||||
mockBlock.nativeArgs.command = "git status"
|
||||
mockTerminal.runCommand.mockReturnValueOnce(neverResolvingProcess)
|
||||
|
||||
await executeCommandTool.handle(mockTask as Task, mockBlock, {
|
||||
askApproval: mockAskApproval,
|
||||
handleError: mockHandleError,
|
||||
pushToolResult: mockPushToolResult,
|
||||
})
|
||||
|
||||
expect(mockPushToolResult).toHaveBeenCalled()
|
||||
const result2 = mockPushToolResult.mock.calls[0][0]
|
||||
expect(result2).toContain("terminated after exceeding")
|
||||
}, 5000)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
|
||||
// Must mock dependencies before importing the handler module.
|
||||
vi.mock("../../../api/providers/fetchers/modelCache")
|
||||
|
||||
import { webviewMessageHandler } from "../webviewMessageHandler"
|
||||
import type { ClineProvider } from "../ClineProvider"
|
||||
|
||||
vi.mock("vscode", () => ({
|
||||
window: {
|
||||
showInformationMessage: vi.fn(),
|
||||
showErrorMessage: vi.fn(),
|
||||
},
|
||||
workspace: {
|
||||
workspaceFolders: [{ uri: { fsPath: "/mock/workspace" } }],
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock imageHelpers - use actual implementations for functions that need real file access
|
||||
vi.mock("../../tools/helpers/imageHelpers", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../tools/helpers/imageHelpers")>()
|
||||
return {
|
||||
...actual,
|
||||
validateImageForProcessing: vi.fn().mockResolvedValue({ isValid: true, sizeInMB: 0.001 }),
|
||||
ImageMemoryTracker: vi.fn().mockImplementation(() => ({
|
||||
getTotalMemoryUsed: vi.fn().mockReturnValue(0),
|
||||
addMemoryUsage: vi.fn(),
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
describe("webviewMessageHandler - image mentions (integration)", () => {
|
||||
it("resolves image mentions for newTask and passes images to createTask", async () => {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "roo-image-mentions-"))
|
||||
try {
|
||||
const imgBytes = Buffer.from("png-bytes")
|
||||
await fs.writeFile(path.join(tmpRoot, "cat.png"), imgBytes)
|
||||
|
||||
const mockProvider = {
|
||||
cwd: tmpRoot,
|
||||
getCurrentTask: vi.fn().mockReturnValue(undefined),
|
||||
createTask: vi.fn().mockResolvedValue(undefined),
|
||||
postMessageToWebview: vi.fn().mockResolvedValue(undefined),
|
||||
getState: vi.fn().mockResolvedValue({
|
||||
maxImageFileSize: 5,
|
||||
maxTotalImageSize: 20,
|
||||
}),
|
||||
} as unknown as ClineProvider
|
||||
|
||||
await webviewMessageHandler(mockProvider, {
|
||||
type: "newTask",
|
||||
text: "Please look at @/cat.png",
|
||||
images: [],
|
||||
} as any)
|
||||
|
||||
expect(mockProvider.createTask).toHaveBeenCalledWith("Please look at @/cat.png", [
|
||||
`data:image/png;base64,${imgBytes.toString("base64")}`,
|
||||
])
|
||||
} finally {
|
||||
await fs.rm(tmpRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it("resolves image mentions for askResponse and passes images to handleWebviewAskResponse", async () => {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "roo-image-mentions-"))
|
||||
try {
|
||||
const imgBytes = Buffer.from("jpg-bytes")
|
||||
await fs.writeFile(path.join(tmpRoot, "cat.jpg"), imgBytes)
|
||||
|
||||
const handleWebviewAskResponse = vi.fn()
|
||||
const mockProvider = {
|
||||
cwd: tmpRoot,
|
||||
getCurrentTask: vi.fn().mockReturnValue({
|
||||
cwd: tmpRoot,
|
||||
handleWebviewAskResponse,
|
||||
}),
|
||||
getState: vi.fn().mockResolvedValue({
|
||||
maxImageFileSize: 5,
|
||||
maxTotalImageSize: 20,
|
||||
}),
|
||||
} as unknown as ClineProvider
|
||||
|
||||
await webviewMessageHandler(mockProvider, {
|
||||
type: "askResponse",
|
||||
askResponse: "messageResponse",
|
||||
text: "Please look at @/cat.jpg",
|
||||
images: [],
|
||||
} as any)
|
||||
|
||||
expect(handleWebviewAskResponse).toHaveBeenCalledWith("messageResponse", "Please look at @/cat.jpg", [
|
||||
`data:image/jpeg;base64,${imgBytes.toString("base64")}`,
|
||||
])
|
||||
} finally {
|
||||
await fs.rm(tmpRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it("resolves gif image mentions (matching read_file behavior)", async () => {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "roo-image-mentions-"))
|
||||
try {
|
||||
const imgBytes = Buffer.from("gif-bytes")
|
||||
await fs.writeFile(path.join(tmpRoot, "animation.gif"), imgBytes)
|
||||
|
||||
const mockProvider = {
|
||||
cwd: tmpRoot,
|
||||
getCurrentTask: vi.fn().mockReturnValue(undefined),
|
||||
createTask: vi.fn().mockResolvedValue(undefined),
|
||||
postMessageToWebview: vi.fn().mockResolvedValue(undefined),
|
||||
getState: vi.fn().mockResolvedValue({
|
||||
maxImageFileSize: 5,
|
||||
maxTotalImageSize: 20,
|
||||
}),
|
||||
} as unknown as ClineProvider
|
||||
|
||||
await webviewMessageHandler(mockProvider, {
|
||||
type: "newTask",
|
||||
text: "See @/animation.gif",
|
||||
images: [],
|
||||
} as any)
|
||||
|
||||
expect(mockProvider.createTask).toHaveBeenCalledWith("See @/animation.gif", [
|
||||
`data:image/gif;base64,${imgBytes.toString("base64")}`,
|
||||
])
|
||||
} finally {
|
||||
await fs.rm(tmpRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue