mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
chore: Convert remaining tool tests to NTC tests
This commit is contained in:
parent
b9018462f1
commit
c0160dc915
4 changed files with 1146 additions and 941 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
567
apps/vscode-e2e/src/suite/tools/write-to-file-native.test.ts
Normal file
567
apps/vscode-e2e/src/suite/tools/write-to-file-native.test.ts
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
import * as assert from "assert"
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
||||
/**
|
||||
* Native tool calling verification state.
|
||||
* Tracks multiple indicators to ensure native protocol is actually being used.
|
||||
*/
|
||||
interface NativeProtocolVerification {
|
||||
/** Whether the apiProtocol field indicates native format (anthropic/openai) */
|
||||
hasNativeApiProtocol: boolean
|
||||
/** The apiProtocol value received (for debugging) */
|
||||
apiProtocol: string | null
|
||||
/** Whether the response text does NOT contain XML tool tags (confirming non-XML) */
|
||||
responseIsNotXML: boolean
|
||||
/** Whether the tool was successfully executed */
|
||||
toolWasExecuted: boolean
|
||||
/** Tool name that was executed (for debugging) */
|
||||
executedToolName: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fresh verification state for tracking native protocol usage.
|
||||
*/
|
||||
function createVerificationState(): NativeProtocolVerification {
|
||||
return {
|
||||
hasNativeApiProtocol: false,
|
||||
apiProtocol: null,
|
||||
responseIsNotXML: true,
|
||||
toolWasExecuted: false,
|
||||
executedToolName: null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that native tool calling was actually used based on the verification state.
|
||||
*/
|
||||
function assertNativeProtocolUsed(verification: NativeProtocolVerification, testName: string): void {
|
||||
assert.ok(verification.apiProtocol !== null, `[${testName}] apiProtocol should be set in api_req_started message.`)
|
||||
|
||||
assert.strictEqual(verification.responseIsNotXML, true, `[${testName}] Response should NOT contain XML tool tags.`)
|
||||
|
||||
assert.strictEqual(
|
||||
verification.toolWasExecuted,
|
||||
true,
|
||||
`[${testName}] Tool should have been executed. Executed tool: ${verification.executedToolName || "none"}`,
|
||||
)
|
||||
|
||||
console.log(`[${testName}] ✓ Native protocol verification passed`)
|
||||
console.log(` - API Protocol: ${verification.apiProtocol}`)
|
||||
console.log(` - Response is not XML: ${verification.responseIsNotXML}`)
|
||||
console.log(` - Tool was executed: ${verification.toolWasExecuted}`)
|
||||
console.log(` - Executed tool name: ${verification.executedToolName || "none"}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message handler that tracks native protocol verification.
|
||||
*/
|
||||
function createNativeVerificationHandler(
|
||||
verification: NativeProtocolVerification,
|
||||
messages: ClineMessage[],
|
||||
options: {
|
||||
onError?: (error: string) => void
|
||||
onToolExecuted?: (toolName: string, details: string) => void
|
||||
debugLogging?: boolean
|
||||
} = {},
|
||||
): (event: { message: ClineMessage }) => void {
|
||||
const { onError, onToolExecuted, debugLogging = true } = options
|
||||
|
||||
return ({ message }: { message: ClineMessage }) => {
|
||||
messages.push(message)
|
||||
|
||||
if (debugLogging) {
|
||||
console.log(`[DEBUG] Message: type=${message.type}, say=${message.say}, ask=${message.ask}`)
|
||||
}
|
||||
|
||||
if (message.type === "say" && message.say === "error") {
|
||||
const errorText = message.text || "Unknown error"
|
||||
console.error("[ERROR]:", errorText)
|
||||
onError?.(errorText)
|
||||
}
|
||||
|
||||
// Track tool execution callbacks
|
||||
if (message.type === "ask" && message.ask === "tool") {
|
||||
if (debugLogging) {
|
||||
console.log("[DEBUG] Tool callback:", message.text?.substring(0, 300))
|
||||
// Extra native-protocol debugging: log full callback payload
|
||||
console.log("[NATIVE-DEBUG] ask/tool raw text:", message.text)
|
||||
}
|
||||
|
||||
try {
|
||||
const toolData = JSON.parse(message.text || "{}")
|
||||
if (debugLogging) {
|
||||
console.log("[NATIVE-DEBUG] parsed tool callback:", JSON.stringify(toolData, null, 2))
|
||||
}
|
||||
if (toolData.tool) {
|
||||
verification.toolWasExecuted = true
|
||||
verification.executedToolName = toolData.tool
|
||||
console.log(`[VERIFIED] Tool executed: ${toolData.tool}`)
|
||||
onToolExecuted?.(toolData.tool, message.text || "")
|
||||
}
|
||||
} catch (_e) {
|
||||
if (debugLogging) {
|
||||
console.log("[DEBUG] Tool callback not JSON:", message.text?.substring(0, 100))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check API request for apiProtocol and tool execution details
|
||||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||
const rawText = message.text
|
||||
if (debugLogging) {
|
||||
console.log("[DEBUG] API request started:", rawText.substring(0, 200))
|
||||
// Extra native-protocol debugging: log full api_req_started payload
|
||||
console.log("[NATIVE-DEBUG] api_req_started raw text:", rawText)
|
||||
}
|
||||
|
||||
// Simple text check first (like original write-to-file.test.ts)
|
||||
if (rawText.includes("write_to_file")) {
|
||||
verification.toolWasExecuted = true
|
||||
verification.executedToolName = verification.executedToolName || "write_to_file"
|
||||
console.log("[VERIFIED] Tool executed via raw text check: write_to_file")
|
||||
onToolExecuted?.("write_to_file", rawText)
|
||||
}
|
||||
|
||||
try {
|
||||
const requestData = JSON.parse(rawText)
|
||||
if (debugLogging) {
|
||||
console.log(
|
||||
"[NATIVE-DEBUG] parsed api_req_started:",
|
||||
// Limit size in case the payload is huge
|
||||
JSON.stringify(requestData, null, 2).substring(0, 5000),
|
||||
)
|
||||
}
|
||||
if (requestData.apiProtocol) {
|
||||
verification.apiProtocol = requestData.apiProtocol
|
||||
if (requestData.apiProtocol === "anthropic" || requestData.apiProtocol === "openai") {
|
||||
verification.hasNativeApiProtocol = true
|
||||
console.log(`[VERIFIED] API Protocol: ${requestData.apiProtocol}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Also check parsed request content
|
||||
if (requestData.request && requestData.request.includes("write_to_file")) {
|
||||
verification.toolWasExecuted = true
|
||||
verification.executedToolName = "write_to_file"
|
||||
console.log(`[VERIFIED] Tool executed via parsed request: write_to_file`)
|
||||
try {
|
||||
const parsed = JSON.parse(requestData.request)
|
||||
if (parsed.request) {
|
||||
onToolExecuted?.("write_to_file", parsed.request)
|
||||
}
|
||||
} catch (_e) {
|
||||
onToolExecuted?.("write_to_file", requestData.request)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("[DEBUG] Failed to parse api_req_started message:", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Check text responses for XML (should NOT be present)
|
||||
if (message.type === "say" && message.say === "text" && message.text) {
|
||||
const hasXMLToolTags =
|
||||
message.text.includes("<write_to_file>") ||
|
||||
message.text.includes("</write_to_file>") ||
|
||||
message.text.includes("<apply_diff>") ||
|
||||
message.text.includes("</apply_diff>")
|
||||
|
||||
if (hasXMLToolTags) {
|
||||
verification.responseIsNotXML = false
|
||||
console.log("[WARNING] Found XML tool tags in response")
|
||||
}
|
||||
}
|
||||
|
||||
if (message.type === "say" && message.say === "completion_result") {
|
||||
if (debugLogging && message.text) {
|
||||
console.log("[DEBUG] AI completion:", message.text.substring(0, 200))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite("Roo Code write_to_file Tool (Native Tool Calling)", function () {
|
||||
setDefaultSuiteTimeout(this)
|
||||
|
||||
let tempDir: string
|
||||
let testFilePath: string
|
||||
|
||||
suiteSetup(async () => {
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "roo-test-native-"))
|
||||
})
|
||||
|
||||
suiteTeardown(async () => {
|
||||
try {
|
||||
await globalThis.api.cancelCurrentTask()
|
||||
} catch {
|
||||
// Task might not be running
|
||||
}
|
||||
await fs.rm(tempDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
setup(async () => {
|
||||
try {
|
||||
await globalThis.api.cancelCurrentTask()
|
||||
} catch {
|
||||
// Task might not be running
|
||||
}
|
||||
|
||||
testFilePath = path.join(tempDir, `test-file-native-${Date.now()}.txt`)
|
||||
await sleep(100)
|
||||
})
|
||||
|
||||
teardown(async () => {
|
||||
try {
|
||||
await globalThis.api.cancelCurrentTask()
|
||||
} catch {
|
||||
// Task might not be running
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.unlink(testFilePath)
|
||||
} catch {
|
||||
// File might not exist
|
||||
}
|
||||
|
||||
await sleep(100)
|
||||
})
|
||||
|
||||
test("Should create a new file with content using native tool calling", async function () {
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
const fileContent = "Hello, this is a test file from native tool calling!"
|
||||
let taskStarted = false
|
||||
let taskCompleted = false
|
||||
let errorOccurred: string | null = null
|
||||
let writeToFileToolExecuted = false
|
||||
let toolExecutionDetails = ""
|
||||
|
||||
const verification = createVerificationState()
|
||||
|
||||
const messageHandler = createNativeVerificationHandler(verification, messages, {
|
||||
onError: (error) => {
|
||||
errorOccurred = error
|
||||
},
|
||||
onToolExecuted: (toolName, details) => {
|
||||
console.log("[TEST-DEBUG] write-to-file createFile onToolExecuted:", toolName)
|
||||
if (
|
||||
toolName === "newFileCreated" ||
|
||||
toolName === "editedExistingFile" ||
|
||||
toolName === "write_to_file" ||
|
||||
toolName === "appliedDiff" ||
|
||||
toolName === "apply_diff"
|
||||
) {
|
||||
writeToFileToolExecuted = true
|
||||
toolExecutionDetails = details
|
||||
console.log("write_to_file tool called!")
|
||||
}
|
||||
},
|
||||
debugLogging: true,
|
||||
})
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
const taskStartedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskStarted = true
|
||||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskCompleted = true
|
||||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
const baseFileName = path.basename(testFilePath)
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowWrite: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
alwaysAllowReadOnlyOutsideWorkspace: true,
|
||||
toolProtocol: "native",
|
||||
apiProvider: "openrouter",
|
||||
apiModelId: "openai/gpt-5.1",
|
||||
},
|
||||
text: `Create a file named "${baseFileName}" with the following content:\n${fileContent}`,
|
||||
})
|
||||
|
||||
console.log("Task ID:", taskId)
|
||||
console.log("Base filename:", baseFileName)
|
||||
console.log("Expecting file at:", testFilePath)
|
||||
|
||||
await waitFor(() => taskStarted, { timeout: 45_000 })
|
||||
if (errorOccurred) {
|
||||
console.error("Early error detected:", errorOccurred)
|
||||
}
|
||||
|
||||
await waitFor(() => taskCompleted, { timeout: 45_000 })
|
||||
await sleep(2000)
|
||||
|
||||
assertNativeProtocolUsed(verification, "createFile")
|
||||
|
||||
const possibleLocations = [
|
||||
testFilePath,
|
||||
path.join(tempDir, baseFileName),
|
||||
path.join(process.cwd(), baseFileName),
|
||||
]
|
||||
|
||||
let fileFound = false
|
||||
let actualFilePath = ""
|
||||
let actualContent = ""
|
||||
|
||||
const workspaceDirs = await fs
|
||||
.readdir("/tmp")
|
||||
.then((files) => files.filter((f) => f.startsWith("roo-test-workspace-")))
|
||||
.catch(() => [])
|
||||
|
||||
for (const wsDir of workspaceDirs) {
|
||||
const wsFilePath = path.join("/tmp", wsDir, baseFileName)
|
||||
try {
|
||||
await fs.access(wsFilePath)
|
||||
fileFound = true
|
||||
actualFilePath = wsFilePath
|
||||
actualContent = await fs.readFile(wsFilePath, "utf-8")
|
||||
console.log("File found in workspace directory:", wsFilePath)
|
||||
break
|
||||
} catch {
|
||||
// Continue checking
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileFound) {
|
||||
for (const location of possibleLocations) {
|
||||
try {
|
||||
await fs.access(location)
|
||||
fileFound = true
|
||||
actualFilePath = location
|
||||
actualContent = await fs.readFile(location, "utf-8")
|
||||
console.log("File found at:", location)
|
||||
break
|
||||
} catch {
|
||||
// Continue checking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileFound) {
|
||||
console.log("File not found in expected locations. Debugging info:")
|
||||
|
||||
try {
|
||||
const tempFiles = await fs.readdir(tempDir)
|
||||
console.log("Files in temp directory:", tempFiles)
|
||||
} catch (e) {
|
||||
console.log("Could not list temp directory:", e)
|
||||
}
|
||||
|
||||
try {
|
||||
const cwdFiles = await fs.readdir(process.cwd())
|
||||
console.log(
|
||||
"Files in CWD:",
|
||||
cwdFiles.filter((f) => f.includes("test-file")),
|
||||
)
|
||||
} catch (e) {
|
||||
console.log("Could not list CWD:", e)
|
||||
}
|
||||
|
||||
try {
|
||||
const tmpFiles = await fs.readdir("/tmp")
|
||||
console.log(
|
||||
"Test files in /tmp:",
|
||||
tmpFiles.filter((f) => f.includes("test-file") || f.includes("roo-test")),
|
||||
)
|
||||
} catch (e) {
|
||||
console.log("Could not list /tmp:", e)
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(fileFound, `File should have been created. Expected filename: ${baseFileName}`)
|
||||
assert.strictEqual(actualContent.trim(), fileContent, "File content should match expected content")
|
||||
assert.ok(writeToFileToolExecuted, "write_to_file tool should have been executed")
|
||||
assert.ok(
|
||||
toolExecutionDetails.includes(baseFileName) || toolExecutionDetails.includes(fileContent),
|
||||
"Tool execution should include the filename or content",
|
||||
)
|
||||
|
||||
console.log("Test passed! File created successfully at:", actualFilePath)
|
||||
console.log("write_to_file tool was properly executed with native tool calling")
|
||||
} finally {
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
test("Should create nested directories when writing file using native tool calling", async function () {
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
const content = "File in nested directory from native tool calling"
|
||||
const fileName = `file-native-${Date.now()}.txt`
|
||||
const nestedPath = path.join(tempDir, "nested-native", "deep", "directory", fileName)
|
||||
let taskStarted = false
|
||||
let taskCompleted = false
|
||||
let writeToFileToolExecuted = false
|
||||
let toolExecutionDetails = ""
|
||||
|
||||
const verification = createVerificationState()
|
||||
|
||||
const messageHandler = createNativeVerificationHandler(verification, messages, {
|
||||
onToolExecuted: (toolName, details) => {
|
||||
console.log("[TEST-DEBUG] write-to-file nestedDirectories onToolExecuted:", toolName)
|
||||
if (
|
||||
toolName === "newFileCreated" ||
|
||||
toolName === "editedExistingFile" ||
|
||||
toolName === "write_to_file" ||
|
||||
toolName === "appliedDiff" ||
|
||||
toolName === "apply_diff"
|
||||
) {
|
||||
writeToFileToolExecuted = true
|
||||
toolExecutionDetails = details
|
||||
console.log("write_to_file tool called!")
|
||||
}
|
||||
},
|
||||
debugLogging: true,
|
||||
})
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
const taskStartedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskStarted = true
|
||||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskCompleted = true
|
||||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowWrite: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
alwaysAllowReadOnlyOutsideWorkspace: true,
|
||||
toolProtocol: "native",
|
||||
apiProvider: "openrouter",
|
||||
apiModelId: "openai/gpt-5.1",
|
||||
},
|
||||
text: `Create a file named "${fileName}" in a nested directory structure "nested-native/deep/directory/" with the following content:\n${content}`,
|
||||
})
|
||||
|
||||
console.log("Task ID:", taskId)
|
||||
console.log("Expected nested path:", nestedPath)
|
||||
|
||||
await waitFor(() => taskStarted, { timeout: 45_000 })
|
||||
await waitFor(() => taskCompleted, { timeout: 45_000 })
|
||||
await sleep(2000)
|
||||
|
||||
assertNativeProtocolUsed(verification, "nestedDirectories")
|
||||
|
||||
let fileFound = false
|
||||
let actualFilePath = ""
|
||||
let actualContent = ""
|
||||
|
||||
const workspaceDirs = await fs
|
||||
.readdir("/tmp")
|
||||
.then((files) => files.filter((f) => f.startsWith("roo-test-workspace-")))
|
||||
.catch(() => [])
|
||||
|
||||
for (const wsDir of workspaceDirs) {
|
||||
const wsNestedPath = path.join("/tmp", wsDir, "nested-native", "deep", "directory", fileName)
|
||||
try {
|
||||
await fs.access(wsNestedPath)
|
||||
fileFound = true
|
||||
actualFilePath = wsNestedPath
|
||||
actualContent = await fs.readFile(wsNestedPath, "utf-8")
|
||||
console.log("File found in workspace nested directory:", wsNestedPath)
|
||||
break
|
||||
} catch {
|
||||
const wsFilePath = path.join("/tmp", wsDir, fileName)
|
||||
try {
|
||||
await fs.access(wsFilePath)
|
||||
fileFound = true
|
||||
actualFilePath = wsFilePath
|
||||
actualContent = await fs.readFile(wsFilePath, "utf-8")
|
||||
console.log("File found in workspace root (nested dirs not created):", wsFilePath)
|
||||
break
|
||||
} catch {
|
||||
// Continue checking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileFound) {
|
||||
try {
|
||||
await fs.access(nestedPath)
|
||||
fileFound = true
|
||||
actualFilePath = nestedPath
|
||||
actualContent = await fs.readFile(nestedPath, "utf-8")
|
||||
console.log("File found at expected nested path:", nestedPath)
|
||||
} catch {
|
||||
// File not found
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileFound) {
|
||||
console.log("File not found. Debugging info:")
|
||||
|
||||
for (const wsDir of workspaceDirs) {
|
||||
const wsPath = path.join("/tmp", wsDir)
|
||||
try {
|
||||
const files = await fs.readdir(wsPath)
|
||||
console.log(`Files in workspace ${wsDir}:`, files)
|
||||
|
||||
const nestedDir = path.join(wsPath, "nested-native")
|
||||
try {
|
||||
await fs.access(nestedDir)
|
||||
console.log("Nested directory exists in workspace")
|
||||
} catch {
|
||||
console.log("Nested directory NOT created in workspace")
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Could not list workspace ${wsDir}:`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(fileFound, `File should have been created. Expected filename: ${fileName}`)
|
||||
assert.strictEqual(actualContent.trim(), content, "File content should match")
|
||||
assert.ok(writeToFileToolExecuted, "write_to_file tool should have been executed")
|
||||
assert.ok(
|
||||
toolExecutionDetails.includes(fileName) ||
|
||||
toolExecutionDetails.includes(content) ||
|
||||
toolExecutionDetails.includes("nested"),
|
||||
"Tool execution should include the filename, content, or nested directory reference",
|
||||
)
|
||||
|
||||
console.log("Test passed! File created successfully at:", actualFilePath)
|
||||
console.log("write_to_file tool was properly executed with native tool calling")
|
||||
} finally {
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -1,448 +0,0 @@
|
|||
import * as assert from "assert"
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
||||
suite.skip("Roo Code write_to_file Tool", function () {
|
||||
setDefaultSuiteTimeout(this)
|
||||
|
||||
let tempDir: string
|
||||
let testFilePath: string
|
||||
|
||||
// Create a temporary directory for test files
|
||||
suiteSetup(async () => {
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "roo-test-"))
|
||||
})
|
||||
|
||||
// Clean up temporary directory after tests
|
||||
suiteTeardown(async () => {
|
||||
// Cancel any running tasks before cleanup
|
||||
try {
|
||||
await globalThis.api.cancelCurrentTask()
|
||||
} catch {
|
||||
// Task might not be running
|
||||
}
|
||||
await fs.rm(tempDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
// Clean up test file before each test
|
||||
setup(async () => {
|
||||
// Cancel any previous task
|
||||
try {
|
||||
await globalThis.api.cancelCurrentTask()
|
||||
} catch {
|
||||
// Task might not be running
|
||||
}
|
||||
|
||||
// Generate unique file name for each test to avoid conflicts
|
||||
testFilePath = path.join(tempDir, `test-file-${Date.now()}.txt`)
|
||||
|
||||
// Small delay to ensure clean state
|
||||
await sleep(100)
|
||||
})
|
||||
|
||||
// Clean up after each test
|
||||
teardown(async () => {
|
||||
// Cancel the current task
|
||||
try {
|
||||
await globalThis.api.cancelCurrentTask()
|
||||
} catch {
|
||||
// Task might not be running
|
||||
}
|
||||
|
||||
// Clean up the test file
|
||||
try {
|
||||
await fs.unlink(testFilePath)
|
||||
} catch {
|
||||
// File might not exist
|
||||
}
|
||||
|
||||
// Small delay to ensure clean state
|
||||
await sleep(100)
|
||||
})
|
||||
|
||||
test("Should create a new file with content", async function () {
|
||||
// Increase timeout for this specific test
|
||||
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
const fileContent = "Hello, this is a test file!"
|
||||
let taskStarted = false
|
||||
let taskCompleted = false
|
||||
let errorOccurred: string | null = null
|
||||
let writeToFileToolExecuted = false
|
||||
let toolExecutionDetails = ""
|
||||
|
||||
// Listen for messages
|
||||
const messageHandler = ({ message }: { message: ClineMessage }) => {
|
||||
messages.push(message)
|
||||
|
||||
// Check for tool execution
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
console.log("Tool execution:", message.text?.substring(0, 200))
|
||||
if (message.text && message.text.includes("write_to_file")) {
|
||||
writeToFileToolExecuted = true
|
||||
toolExecutionDetails = message.text
|
||||
// Try to parse the tool execution details
|
||||
try {
|
||||
const parsed = JSON.parse(message.text)
|
||||
console.log("write_to_file tool called with request:", parsed.request?.substring(0, 300))
|
||||
} catch (_e) {
|
||||
console.log("Could not parse tool execution details")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log important messages for debugging
|
||||
if (message.type === "say" && message.say === "error") {
|
||||
errorOccurred = message.text || "Unknown error"
|
||||
console.error("Error:", message.text)
|
||||
}
|
||||
if (message.type === "ask" && message.ask === "tool") {
|
||||
console.log("Tool request:", message.text?.substring(0, 200))
|
||||
}
|
||||
if (message.type === "say" && (message.say === "completion_result" || message.say === "text")) {
|
||||
console.log("AI response:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskStarted = true
|
||||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskCompleted = true
|
||||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
// Start task with a very simple prompt
|
||||
const baseFileName = path.basename(testFilePath)
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowWrite: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
alwaysAllowReadOnlyOutsideWorkspace: true,
|
||||
},
|
||||
text: `Create a file named "${baseFileName}" with the following content:\n${fileContent}`,
|
||||
})
|
||||
|
||||
console.log("Task ID:", taskId)
|
||||
console.log("Base filename:", baseFileName)
|
||||
console.log("Expecting file at:", testFilePath)
|
||||
|
||||
// Wait for task to start
|
||||
await waitFor(() => taskStarted, { timeout: 45_000 })
|
||||
|
||||
// Check for early errors
|
||||
if (errorOccurred) {
|
||||
console.error("Early error detected:", errorOccurred)
|
||||
}
|
||||
|
||||
// Wait for task completion
|
||||
await waitFor(() => taskCompleted, { timeout: 45_000 })
|
||||
|
||||
// Give extra time for file system operations
|
||||
await sleep(2000)
|
||||
|
||||
// The file might be created in different locations, let's check them all
|
||||
const possibleLocations = [
|
||||
testFilePath, // Expected location
|
||||
path.join(tempDir, baseFileName), // In temp directory
|
||||
path.join(process.cwd(), baseFileName), // In current working directory
|
||||
path.join("/tmp/roo-test-workspace-" + "*", baseFileName), // In workspace created by runTest.ts
|
||||
]
|
||||
|
||||
let fileFound = false
|
||||
let actualFilePath = ""
|
||||
let actualContent = ""
|
||||
|
||||
// First check the workspace directory that was created
|
||||
const workspaceDirs = await fs
|
||||
.readdir("/tmp")
|
||||
.then((files) => files.filter((f) => f.startsWith("roo-test-workspace-")))
|
||||
.catch(() => [])
|
||||
|
||||
for (const wsDir of workspaceDirs) {
|
||||
const wsFilePath = path.join("/tmp", wsDir, baseFileName)
|
||||
try {
|
||||
await fs.access(wsFilePath)
|
||||
fileFound = true
|
||||
actualFilePath = wsFilePath
|
||||
actualContent = await fs.readFile(wsFilePath, "utf-8")
|
||||
console.log("File found in workspace directory:", wsFilePath)
|
||||
break
|
||||
} catch {
|
||||
// Continue checking
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in workspace, check other locations
|
||||
if (!fileFound) {
|
||||
for (const location of possibleLocations) {
|
||||
try {
|
||||
await fs.access(location)
|
||||
fileFound = true
|
||||
actualFilePath = location
|
||||
actualContent = await fs.readFile(location, "utf-8")
|
||||
console.log("File found at:", location)
|
||||
break
|
||||
} catch {
|
||||
// Continue checking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found, list directories to help debug
|
||||
if (!fileFound) {
|
||||
console.log("File not found in expected locations. Debugging info:")
|
||||
|
||||
// List temp directory
|
||||
try {
|
||||
const tempFiles = await fs.readdir(tempDir)
|
||||
console.log("Files in temp directory:", tempFiles)
|
||||
} catch (e) {
|
||||
console.log("Could not list temp directory:", e)
|
||||
}
|
||||
|
||||
// List current working directory
|
||||
try {
|
||||
const cwdFiles = await fs.readdir(process.cwd())
|
||||
console.log(
|
||||
"Files in CWD:",
|
||||
cwdFiles.filter((f) => f.includes("test-file")),
|
||||
)
|
||||
} catch (e) {
|
||||
console.log("Could not list CWD:", e)
|
||||
}
|
||||
|
||||
// List /tmp for test files
|
||||
try {
|
||||
const tmpFiles = await fs.readdir("/tmp")
|
||||
console.log(
|
||||
"Test files in /tmp:",
|
||||
tmpFiles.filter((f) => f.includes("test-file") || f.includes("roo-test")),
|
||||
)
|
||||
} catch (e) {
|
||||
console.log("Could not list /tmp:", e)
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(fileFound, `File should have been created. Expected filename: ${baseFileName}`)
|
||||
assert.strictEqual(actualContent.trim(), fileContent, "File content should match expected content")
|
||||
|
||||
// Verify that write_to_file tool was actually executed
|
||||
assert.ok(writeToFileToolExecuted, "write_to_file tool should have been executed")
|
||||
assert.ok(
|
||||
toolExecutionDetails.includes(baseFileName) || toolExecutionDetails.includes(fileContent),
|
||||
"Tool execution should include the filename or content",
|
||||
)
|
||||
|
||||
console.log("Test passed! File created successfully at:", actualFilePath)
|
||||
console.log("write_to_file tool was properly executed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
test("Should create nested directories when writing file", async function () {
|
||||
// Increase timeout for this specific test
|
||||
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
const content = "File in nested directory"
|
||||
const fileName = `file-${Date.now()}.txt`
|
||||
const nestedPath = path.join(tempDir, "nested", "deep", "directory", fileName)
|
||||
let taskStarted = false
|
||||
let taskCompleted = false
|
||||
let writeToFileToolExecuted = false
|
||||
let toolExecutionDetails = ""
|
||||
|
||||
// Listen for messages
|
||||
const messageHandler = ({ message }: { message: ClineMessage }) => {
|
||||
messages.push(message)
|
||||
|
||||
// Check for tool execution
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
console.log("Tool execution:", message.text?.substring(0, 200))
|
||||
if (message.text && message.text.includes("write_to_file")) {
|
||||
writeToFileToolExecuted = true
|
||||
toolExecutionDetails = message.text
|
||||
// Try to parse the tool execution details
|
||||
try {
|
||||
const parsed = JSON.parse(message.text)
|
||||
console.log("write_to_file tool called with request:", parsed.request?.substring(0, 300))
|
||||
} catch (_e) {
|
||||
console.log("Could not parse tool execution details")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.type === "ask" && message.ask === "tool") {
|
||||
console.log("Tool request:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskStarted = true
|
||||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskCompleted = true
|
||||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
// Start task to create file in nested directory
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowWrite: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
alwaysAllowReadOnlyOutsideWorkspace: true,
|
||||
},
|
||||
text: `Create a file named "${fileName}" in a nested directory structure "nested/deep/directory/" with the following content:\n${content}`,
|
||||
})
|
||||
|
||||
console.log("Task ID:", taskId)
|
||||
console.log("Expected nested path:", nestedPath)
|
||||
|
||||
// Wait for task to start
|
||||
await waitFor(() => taskStarted, { timeout: 45_000 })
|
||||
|
||||
// Wait for task completion
|
||||
await waitFor(() => taskCompleted, { timeout: 45_000 })
|
||||
|
||||
// Give extra time for file system operations
|
||||
await sleep(2000)
|
||||
|
||||
// Check various possible locations
|
||||
let fileFound = false
|
||||
let actualFilePath = ""
|
||||
let actualContent = ""
|
||||
|
||||
// Check workspace directories
|
||||
const workspaceDirs = await fs
|
||||
.readdir("/tmp")
|
||||
.then((files) => files.filter((f) => f.startsWith("roo-test-workspace-")))
|
||||
.catch(() => [])
|
||||
|
||||
for (const wsDir of workspaceDirs) {
|
||||
// Check in nested structure within workspace
|
||||
const wsNestedPath = path.join("/tmp", wsDir, "nested", "deep", "directory", fileName)
|
||||
try {
|
||||
await fs.access(wsNestedPath)
|
||||
fileFound = true
|
||||
actualFilePath = wsNestedPath
|
||||
actualContent = await fs.readFile(wsNestedPath, "utf-8")
|
||||
console.log("File found in workspace nested directory:", wsNestedPath)
|
||||
break
|
||||
} catch {
|
||||
// Also check if file was created directly in workspace root
|
||||
const wsFilePath = path.join("/tmp", wsDir, fileName)
|
||||
try {
|
||||
await fs.access(wsFilePath)
|
||||
fileFound = true
|
||||
actualFilePath = wsFilePath
|
||||
actualContent = await fs.readFile(wsFilePath, "utf-8")
|
||||
console.log("File found in workspace root (nested dirs not created):", wsFilePath)
|
||||
break
|
||||
} catch {
|
||||
// Continue checking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in workspace, check the expected location
|
||||
if (!fileFound) {
|
||||
try {
|
||||
await fs.access(nestedPath)
|
||||
fileFound = true
|
||||
actualFilePath = nestedPath
|
||||
actualContent = await fs.readFile(nestedPath, "utf-8")
|
||||
console.log("File found at expected nested path:", nestedPath)
|
||||
} catch {
|
||||
// File not found
|
||||
}
|
||||
}
|
||||
|
||||
// Debug output if file not found
|
||||
if (!fileFound) {
|
||||
console.log("File not found. Debugging info:")
|
||||
|
||||
// List workspace directories and their contents
|
||||
for (const wsDir of workspaceDirs) {
|
||||
const wsPath = path.join("/tmp", wsDir)
|
||||
try {
|
||||
const files = await fs.readdir(wsPath)
|
||||
console.log(`Files in workspace ${wsDir}:`, files)
|
||||
|
||||
// Check if nested directory was created
|
||||
const nestedDir = path.join(wsPath, "nested")
|
||||
try {
|
||||
await fs.access(nestedDir)
|
||||
console.log("Nested directory exists in workspace")
|
||||
} catch {
|
||||
console.log("Nested directory NOT created in workspace")
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Could not list workspace ${wsDir}:`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(fileFound, `File should have been created. Expected filename: ${fileName}`)
|
||||
assert.strictEqual(actualContent.trim(), content, "File content should match")
|
||||
|
||||
// Verify that write_to_file tool was actually executed
|
||||
assert.ok(writeToFileToolExecuted, "write_to_file tool should have been executed")
|
||||
assert.ok(
|
||||
toolExecutionDetails.includes(fileName) ||
|
||||
toolExecutionDetails.includes(content) ||
|
||||
toolExecutionDetails.includes("nested"),
|
||||
"Tool execution should include the filename, content, or nested directory reference",
|
||||
)
|
||||
|
||||
// Note: We're not checking if the nested directory structure was created,
|
||||
// just that the file exists with the correct content
|
||||
console.log("Test passed! File created successfully at:", actualFilePath)
|
||||
console.log("write_to_file tool was properly executed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue