mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-12 23:01:21 +00:00
feat: add integration tests for list_code_definition_names tool
- Add comprehensive E2E tests for the list_code_definition_names tool - Test TypeScript, JavaScript, Python, and Go file parsing - Test directory analysis functionality - Test error handling for non-existent files - Test handling of empty files - Test nested directory file analysis - Create test fixtures with various code definitions (interfaces, classes, functions, enums, etc.) - Implement proper test setup and teardown procedures - Follow established Mocha TDD patterns with suite/test blocks
This commit is contained in:
parent
72cb248ef8
commit
f8b36f2459
1 changed files with 987 additions and 0 deletions
|
|
@ -0,0 +1,987 @@
|
|||
import * as assert from "assert"
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitUntilCompleted, sleep } from "../utils"
|
||||
|
||||
suite("Roo Code list_code_definition_names Tool", () => {
|
||||
let workspaceDir: string
|
||||
let testFiles: {
|
||||
tsFile: string
|
||||
jsFile: string
|
||||
pyFile: string
|
||||
goFile: string
|
||||
emptyFile: string
|
||||
nestedTsFile: string
|
||||
testDir: string
|
||||
nestedDir: string
|
||||
}
|
||||
let taskId: string
|
||||
|
||||
// Create test files before all tests
|
||||
suiteSetup(async () => {
|
||||
// Get workspace directory
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||
throw new Error("No workspace folder found")
|
||||
}
|
||||
workspaceDir = workspaceFolders[0]!.uri.fsPath
|
||||
|
||||
// Create test directory structure
|
||||
const testDirName = `test-definitions-${Date.now()}`
|
||||
const testDir = path.join(workspaceDir, testDirName)
|
||||
const nestedDir = path.join(testDir, "nested")
|
||||
await fs.mkdir(testDir, { recursive: true })
|
||||
await fs.mkdir(nestedDir, { recursive: true })
|
||||
|
||||
// Define test file paths - all files go in the test directory
|
||||
testFiles = {
|
||||
tsFile: path.join(testDir, `definitions.ts`),
|
||||
jsFile: path.join(testDir, `functions.js`),
|
||||
pyFile: path.join(testDir, `classes.py`),
|
||||
goFile: path.join(testDir, `structs.go`),
|
||||
emptyFile: path.join(testDir, `empty.txt`),
|
||||
nestedTsFile: path.join(nestedDir, `nested-definitions.ts`),
|
||||
testDir: testDir,
|
||||
nestedDir: nestedDir,
|
||||
}
|
||||
|
||||
// Create TypeScript file with various definitions
|
||||
await fs.writeFile(
|
||||
testFiles.tsFile,
|
||||
`// TypeScript definitions test file
|
||||
export interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: number
|
||||
title: string
|
||||
price: number
|
||||
category: string
|
||||
}
|
||||
|
||||
export class UserService {
|
||||
private users: User[] = []
|
||||
|
||||
async getUser(id: number): Promise<User | undefined> {
|
||||
return this.users.find(u => u.id === id)
|
||||
}
|
||||
|
||||
async createUser(user: User): Promise<void> {
|
||||
this.users.push(user)
|
||||
}
|
||||
|
||||
async updateUser(id: number, updates: Partial<User>): Promise<void> {
|
||||
const index = this.users.findIndex(u => u.id === id)
|
||||
if (index !== -1) {
|
||||
this.users[index] = { ...this.users[index], ...updates }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateDiscount(price: number, percentage: number): number {
|
||||
return price * (1 - percentage / 100)
|
||||
}
|
||||
|
||||
export const API_ENDPOINT = "https://api.example.com"
|
||||
export const MAX_RETRIES = 3
|
||||
|
||||
type Status = "active" | "inactive" | "pending"
|
||||
|
||||
enum OrderStatus {
|
||||
Pending = "PENDING",
|
||||
Processing = "PROCESSING",
|
||||
Completed = "COMPLETED",
|
||||
Cancelled = "CANCELLED"
|
||||
}`,
|
||||
)
|
||||
|
||||
// Create JavaScript file with functions and classes
|
||||
await fs.writeFile(
|
||||
testFiles.jsFile,
|
||||
`// JavaScript functions test file
|
||||
function calculateTotal(items) {
|
||||
return items.reduce((sum, item) => sum + item.price, 0)
|
||||
}
|
||||
|
||||
function validateEmail(email) {
|
||||
const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/
|
||||
return emailRegex.test(email)
|
||||
}
|
||||
|
||||
class ShoppingCart {
|
||||
constructor() {
|
||||
this.items = []
|
||||
}
|
||||
|
||||
addItem(item) {
|
||||
this.items.push(item)
|
||||
}
|
||||
|
||||
removeItem(itemId) {
|
||||
this.items = this.items.filter(item => item.id !== itemId)
|
||||
}
|
||||
|
||||
getTotal() {
|
||||
return calculateTotal(this.items)
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.items = []
|
||||
}
|
||||
}
|
||||
|
||||
const formatCurrency = (amount) => {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD'
|
||||
}).format(amount)
|
||||
}
|
||||
|
||||
// Arrow function
|
||||
const multiply = (a, b) => a * b
|
||||
|
||||
// Async function
|
||||
async function fetchUserData(userId) {
|
||||
const response = await fetch(\`/api/users/\${userId}\`)
|
||||
return response.json()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
calculateTotal,
|
||||
validateEmail,
|
||||
ShoppingCart,
|
||||
formatCurrency,
|
||||
multiply,
|
||||
fetchUserData
|
||||
}`,
|
||||
)
|
||||
|
||||
// Create Python file with classes and functions
|
||||
await fs.writeFile(
|
||||
testFiles.pyFile,
|
||||
`# Python classes test file
|
||||
import datetime
|
||||
from typing import List, Optional, Dict
|
||||
|
||||
class Animal:
|
||||
"""Base class for animals"""
|
||||
def __init__(self, name: str, species: str):
|
||||
self.name = name
|
||||
self.species = species
|
||||
|
||||
def make_sound(self) -> str:
|
||||
raise NotImplementedError("Subclasses must implement make_sound")
|
||||
|
||||
class Dog(Animal):
|
||||
"""Dog class inheriting from Animal"""
|
||||
def __init__(self, name: str, breed: str):
|
||||
super().__init__(name, "Canis familiaris")
|
||||
self.breed = breed
|
||||
|
||||
def make_sound(self) -> str:
|
||||
return "Woof!"
|
||||
|
||||
def fetch(self, item: str) -> str:
|
||||
return f"{self.name} fetched the {item}"
|
||||
|
||||
class Cat(Animal):
|
||||
"""Cat class inheriting from Animal"""
|
||||
def __init__(self, name: str, color: str):
|
||||
super().__init__(name, "Felis catus")
|
||||
self.color = color
|
||||
|
||||
def make_sound(self) -> str:
|
||||
return "Meow!"
|
||||
|
||||
def calculate_age(birth_date: datetime.date) -> int:
|
||||
"""Calculate age from birth date"""
|
||||
today = datetime.date.today()
|
||||
age = today.year - birth_date.year
|
||||
if today.month < birth_date.month or (today.month == birth_date.month and today.day < birth_date.day):
|
||||
age -= 1
|
||||
return age
|
||||
|
||||
def process_data(data: List[Dict[str, any]]) -> Dict[str, any]:
|
||||
"""Process a list of data dictionaries"""
|
||||
result = {
|
||||
"count": len(data),
|
||||
"items": data,
|
||||
"processed_at": datetime.datetime.now().isoformat()
|
||||
}
|
||||
return result
|
||||
|
||||
async def fetch_remote_data(url: str) -> Optional[Dict]:
|
||||
"""Async function to fetch remote data"""
|
||||
# Simulated async operation
|
||||
return {"url": url, "status": "success"}
|
||||
|
||||
# Global variables
|
||||
MAX_CONNECTIONS = 100
|
||||
DEFAULT_TIMEOUT = 30`,
|
||||
)
|
||||
|
||||
// Create Go file with structs and functions
|
||||
await fs.writeFile(
|
||||
testFiles.goFile,
|
||||
`// Go structs test file
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// User represents a user in the system
|
||||
type User struct {
|
||||
ID int
|
||||
Name string
|
||||
Email string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// Product represents a product
|
||||
type Product struct {
|
||||
ID int
|
||||
Name string
|
||||
Description string
|
||||
Price float64
|
||||
Stock int
|
||||
}
|
||||
|
||||
// OrderItem represents an item in an order
|
||||
type OrderItem struct {
|
||||
Product Product
|
||||
Quantity int
|
||||
}
|
||||
|
||||
// Order represents a customer order
|
||||
type Order struct {
|
||||
ID int
|
||||
UserID int
|
||||
Items []OrderItem
|
||||
TotalPrice float64
|
||||
OrderDate time.Time
|
||||
Status string
|
||||
}
|
||||
|
||||
// CalculateTotal calculates the total price of an order
|
||||
func CalculateTotal(items []OrderItem) float64 {
|
||||
total := 0.0
|
||||
for _, item := range items {
|
||||
total += item.Product.Price * float64(item.Quantity)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// ValidateUser validates user data
|
||||
func ValidateUser(user User) error {
|
||||
if user.Name == "" {
|
||||
return fmt.Errorf("user name cannot be empty")
|
||||
}
|
||||
if user.Email == "" {
|
||||
return fmt.Errorf("user email cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByID retrieves a user by ID
|
||||
func GetUserByID(id int) (*User, error) {
|
||||
// Simulated database lookup
|
||||
return &User{
|
||||
ID: id,
|
||||
Name: "John Doe",
|
||||
Email: "john@example.com",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Constants
|
||||
const (
|
||||
MaxRetries = 3
|
||||
DefaultTimeout = 30
|
||||
APIVersion = "v1"
|
||||
)
|
||||
|
||||
// Variables
|
||||
var (
|
||||
connectionPool []string
|
||||
isInitialized bool
|
||||
)`,
|
||||
)
|
||||
|
||||
// Create empty file
|
||||
await fs.writeFile(testFiles.emptyFile, "")
|
||||
|
||||
// Create nested TypeScript file
|
||||
await fs.writeFile(
|
||||
testFiles.nestedTsFile,
|
||||
`// Nested TypeScript file
|
||||
export class NestedService {
|
||||
private data: any[] = []
|
||||
|
||||
getData(): any[] {
|
||||
return this.data
|
||||
}
|
||||
|
||||
addData(item: any): void {
|
||||
this.data.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
export interface NestedConfig {
|
||||
enabled: boolean
|
||||
timeout: number
|
||||
retries: number
|
||||
}
|
||||
|
||||
export function processNestedData(data: any[]): any[] {
|
||||
return data.filter(item => item !== null)
|
||||
}`,
|
||||
)
|
||||
})
|
||||
|
||||
// Clean up test files after all tests
|
||||
suiteTeardown(async () => {
|
||||
// Cancel any running tasks before cleanup
|
||||
try {
|
||||
await globalThis.api.cancelCurrentTask()
|
||||
} catch {
|
||||
// Task might not be running
|
||||
}
|
||||
|
||||
// Clean up test files
|
||||
for (const [key, filePath] of Object.entries(testFiles)) {
|
||||
if (key === "testDir" || key === "nestedDir") {
|
||||
// Skip directory entries for now
|
||||
continue
|
||||
}
|
||||
try {
|
||||
await fs.unlink(filePath)
|
||||
} catch (_error) {
|
||||
// Failed to delete file
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up directories
|
||||
try {
|
||||
await fs.rmdir(testFiles.nestedDir)
|
||||
await fs.rmdir(testFiles.testDir)
|
||||
} catch (_error) {
|
||||
// Failed to delete test directories
|
||||
}
|
||||
})
|
||||
|
||||
// Clean up before each test
|
||||
setup(async () => {
|
||||
// Cancel any previous task
|
||||
try {
|
||||
await globalThis.api.cancelCurrentTask()
|
||||
} catch {
|
||||
// Task might not be running
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Small delay to ensure clean state
|
||||
await sleep(100)
|
||||
})
|
||||
|
||||
test("Should list code definitions from a TypeScript file", async function () {
|
||||
this.timeout(90_000)
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
let _taskCompleted = false
|
||||
let toolExecuted = false
|
||||
let definitionsFound: string | null = null
|
||||
|
||||
// Listen for messages
|
||||
const messageHandler = ({ message }: { message: ClineMessage }) => {
|
||||
messages.push(message)
|
||||
|
||||
// Check for tool execution and capture results
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
const text = message.text || ""
|
||||
if (text.includes("list_code_definition_names")) {
|
||||
toolExecuted = true
|
||||
|
||||
// Extract tool results from the request
|
||||
try {
|
||||
const jsonMatch = text.match(/\{.*\}/s)
|
||||
if (jsonMatch) {
|
||||
const data = JSON.parse(jsonMatch[0])
|
||||
|
||||
// Check if it's the tool response
|
||||
if (data.tool === "listCodeDefinitionNames" && data.content) {
|
||||
definitionsFound = data.content
|
||||
}
|
||||
// Check if it's in the request field
|
||||
else if (data.request) {
|
||||
// Look for Result: in the request
|
||||
const resultMatch = data.request.match(/Result:\s*([\s\S]*?)(?:\n\n|$)/)
|
||||
if (resultMatch) {
|
||||
definitionsFound = resultMatch[1].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_e) {
|
||||
// Failed to parse, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for ask tool messages
|
||||
if (message.type === "ask" && message.ask === "tool") {
|
||||
const text = message.text || ""
|
||||
try {
|
||||
const data = JSON.parse(text)
|
||||
if (data.tool === "listCodeDefinitionNames" && data.content) {
|
||||
definitionsFound = data.content
|
||||
}
|
||||
} catch (_e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.on("message", messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const completionHandler = (completedTaskId: string) => {
|
||||
if (completedTaskId === taskId) {
|
||||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", completionHandler)
|
||||
|
||||
try {
|
||||
// Start task to list code definitions from TypeScript file
|
||||
const testDirName = path.basename(testFiles.testDir)
|
||||
const fileName = path.basename(testFiles.tsFile)
|
||||
const relativePath = path.join(testDirName, fileName)
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
},
|
||||
text: `I have created a test directory "${testDirName}" in the workspace. Use the list_code_definition_names tool to analyze the TypeScript file at path "${relativePath}". This file contains interfaces (User, Product), classes (UserService), functions (calculateDiscount), and enums (OrderStatus). The file exists in the workspace.`,
|
||||
})
|
||||
|
||||
// Wait for task completion
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Verify results
|
||||
assert.strictEqual(toolExecuted, true, "list_code_definition_names tool should have been executed")
|
||||
|
||||
// Log messages for debugging if definitionsFound is null
|
||||
if (!definitionsFound) {
|
||||
console.log(
|
||||
"No definitions found. Messages received:",
|
||||
messages.map((m) => ({
|
||||
type: m.type,
|
||||
say: m.say,
|
||||
ask: m.ask,
|
||||
text: m.text?.substring(0, 200) + (m.text && m.text.length > 200 ? "..." : ""),
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
assert.ok(definitionsFound, `Should have found code definitions, but got: ${definitionsFound}`)
|
||||
|
||||
// Simple verification - just check that we got some definitions
|
||||
if (definitionsFound) {
|
||||
const defs = definitionsFound as string
|
||||
// Basic checks - the tool should find some key items
|
||||
assert.ok(
|
||||
defs.includes("User"),
|
||||
`Should find User interface in definitions: ${defs.substring(0, 500)}...`,
|
||||
)
|
||||
assert.ok(
|
||||
defs.includes("calculateDiscount"),
|
||||
`Should find calculateDiscount function in definitions: ${defs.substring(0, 500)}...`,
|
||||
)
|
||||
assert.ok(defs.length > 100, `Should have substantial content, but got length: ${defs.length}`)
|
||||
}
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", completionHandler)
|
||||
}
|
||||
})
|
||||
|
||||
test("Should list code definitions from a JavaScript file", async function () {
|
||||
this.timeout(90_000)
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
let _taskCompleted = false
|
||||
let toolExecuted = false
|
||||
let definitionsFound: string | null = null
|
||||
|
||||
// Listen for messages
|
||||
const messageHandler = ({ message }: { message: ClineMessage }) => {
|
||||
messages.push(message)
|
||||
|
||||
// Check for tool execution
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
const text = message.text || ""
|
||||
if (text.includes("list_code_definition_names") || text.includes("listCodeDefinitionNames")) {
|
||||
toolExecuted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tool results in ask messages
|
||||
if (message.type === "ask" && message.ask === "tool") {
|
||||
const text = message.text || ""
|
||||
try {
|
||||
const data = JSON.parse(text)
|
||||
if (data.tool === "listCodeDefinitionNames" && data.approvalState === "approved" && data.content) {
|
||||
definitionsFound = data.content
|
||||
}
|
||||
} catch (_e) {
|
||||
// Not JSON or parsing failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.on("message", messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const completionHandler = (completedTaskId: string) => {
|
||||
if (completedTaskId === taskId) {
|
||||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", completionHandler)
|
||||
|
||||
try {
|
||||
// Start task to list code definitions from JavaScript file
|
||||
const testDirName = path.basename(testFiles.testDir)
|
||||
const fileName = path.basename(testFiles.jsFile)
|
||||
const relativePath = path.join(testDirName, fileName)
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
},
|
||||
text: `Use the list_code_definition_names tool to analyze the JavaScript file at path "${relativePath}". This file exists in the workspace test directory and contains functions (calculateTotal, validateEmail, fetchUserData) and a class (ShoppingCart).`,
|
||||
})
|
||||
|
||||
// Wait for task completion
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Verify results
|
||||
assert.strictEqual(toolExecuted, true, "list_code_definition_names tool should have been executed")
|
||||
|
||||
// Log messages for debugging if definitionsFound is null
|
||||
if (!definitionsFound) {
|
||||
console.log(
|
||||
"No definitions found. Messages received:",
|
||||
messages.map((m) => ({
|
||||
type: m.type,
|
||||
say: m.say,
|
||||
ask: m.ask,
|
||||
text: m.text?.substring(0, 200) + (m.text && m.text.length > 200 ? "..." : ""),
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
assert.ok(definitionsFound, `Should have found code definitions, but got: ${definitionsFound}`)
|
||||
|
||||
// Simple verification
|
||||
if (definitionsFound) {
|
||||
const defs = definitionsFound as string
|
||||
// Basic checks
|
||||
assert.ok(
|
||||
defs.includes("calculateTotal"),
|
||||
`Should find calculateTotal function in definitions: ${defs.substring(0, 500)}...`,
|
||||
)
|
||||
assert.ok(
|
||||
defs.includes("ShoppingCart"),
|
||||
`Should find ShoppingCart class in definitions: ${defs.substring(0, 500)}...`,
|
||||
)
|
||||
assert.ok(defs.length > 100, `Should have substantial content, but got length: ${defs.length}`)
|
||||
}
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", completionHandler)
|
||||
}
|
||||
})
|
||||
|
||||
test("Should list code definitions from a directory", async function () {
|
||||
this.timeout(90_000)
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
let _taskCompleted = false
|
||||
let toolExecuted = false
|
||||
let definitionsFound: string | null = null
|
||||
let taskId: string
|
||||
|
||||
// Listen for messages
|
||||
const messageHandler = ({ message }: { message: ClineMessage }) => {
|
||||
messages.push(message)
|
||||
|
||||
// Check for tool execution
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
const text = message.text || ""
|
||||
if (text.includes("list_code_definition_names") || text.includes("listCodeDefinitionNames")) {
|
||||
toolExecuted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tool results in ask messages
|
||||
if (message.type === "ask" && message.ask === "tool") {
|
||||
const text = message.text || ""
|
||||
try {
|
||||
const data = JSON.parse(text)
|
||||
if (data.tool === "listCodeDefinitionNames" && data.approvalState === "approved" && data.content) {
|
||||
definitionsFound = data.content
|
||||
}
|
||||
} catch (_e) {
|
||||
// Not JSON or parsing failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.on("message", messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const completionHandler = (completedTaskId: string) => {
|
||||
if (completedTaskId === taskId) {
|
||||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", completionHandler)
|
||||
|
||||
try {
|
||||
// Start task to list code definitions from the test directory
|
||||
const testDirName = path.basename(testFiles.testDir)
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
},
|
||||
text: `Use the list_code_definition_names tool to analyze all files in the directory "${testDirName}". This directory exists in the workspace and contains TypeScript, JavaScript, Python, and Go source files.`,
|
||||
})
|
||||
|
||||
// Wait for task completion
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Verify results
|
||||
assert.strictEqual(toolExecuted, true, "list_code_definition_names tool should have been executed")
|
||||
|
||||
// Log messages for debugging if definitionsFound is null
|
||||
if (!definitionsFound) {
|
||||
console.log(
|
||||
"No definitions found. Messages received:",
|
||||
messages.map((m) => ({
|
||||
type: m.type,
|
||||
say: m.say,
|
||||
ask: m.ask,
|
||||
text: m.text?.substring(0, 200) + (m.text && m.text.length > 200 ? "..." : ""),
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
definitionsFound,
|
||||
`Should have found code definitions from multiple files, but got: ${definitionsFound}`,
|
||||
)
|
||||
|
||||
// Simple verification - just check that multiple files were processed
|
||||
if (definitionsFound) {
|
||||
const defs = definitionsFound as string
|
||||
// Basic checks - should find at least some files
|
||||
assert.ok(
|
||||
defs.includes(".ts") || defs.includes(".js"),
|
||||
`Should include some source files in definitions: ${defs.substring(0, 500)}...`,
|
||||
)
|
||||
assert.ok(
|
||||
defs.length > 200,
|
||||
`Should have substantial content from multiple files, but got length: ${defs.length}`,
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", completionHandler)
|
||||
}
|
||||
})
|
||||
|
||||
test("Should handle non-existent file gracefully", async function () {
|
||||
this.timeout(90_000)
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
let _taskCompleted = false
|
||||
let toolExecuted = false
|
||||
let errorMessage: string | null = null
|
||||
let taskId: string
|
||||
|
||||
// Listen for messages
|
||||
const messageHandler = ({ message }: { message: ClineMessage }) => {
|
||||
messages.push(message)
|
||||
|
||||
// Check for tool execution
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
const text = message.text || ""
|
||||
if (text.includes("list_code_definition_names")) {
|
||||
toolExecuted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for error or result
|
||||
if (message.type === "say" && (message.say === "completion_result" || message.say === "text")) {
|
||||
const text = message.text || ""
|
||||
if (text.includes("does not exist") || text.includes("cannot be accessed") || text.includes("error")) {
|
||||
errorMessage = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.on("message", messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const completionHandler = (completedTaskId: string) => {
|
||||
if (completedTaskId === taskId) {
|
||||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", completionHandler)
|
||||
|
||||
try {
|
||||
// Start task to list code definitions from non-existent file
|
||||
const testDirName = path.basename(testFiles.testDir)
|
||||
const nonExistentFile = `non-existent-file-${Date.now()}.ts`
|
||||
const relativePath = path.join(testDirName, nonExistentFile)
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
},
|
||||
text: `Use the list_code_definition_names tool to analyze the file at path "${relativePath}". This file does not exist, which is expected for this test. Please use the tool anyway and report that the file cannot be found, then complete the task.`,
|
||||
})
|
||||
|
||||
// Wait for task completion
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Verify results
|
||||
assert.strictEqual(toolExecuted, true, "list_code_definition_names tool should have been executed")
|
||||
assert.ok(errorMessage, "Should have received an error message for non-existent file")
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", completionHandler)
|
||||
}
|
||||
})
|
||||
|
||||
test("Should handle empty file gracefully", async function () {
|
||||
this.timeout(90_000)
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
let _taskCompleted = false
|
||||
let toolExecuted = false
|
||||
let resultMessage: string | null = null
|
||||
let taskId: string
|
||||
|
||||
// Listen for messages
|
||||
const messageHandler = ({ message }: { message: ClineMessage }) => {
|
||||
messages.push(message)
|
||||
|
||||
// Check for tool execution
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
const text = message.text || ""
|
||||
if (text.includes("list_code_definition_names")) {
|
||||
toolExecuted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for result
|
||||
if (message.type === "say" && (message.say === "completion_result" || message.say === "text")) {
|
||||
const text = message.text || ""
|
||||
if (text.includes("definition") || text.includes("empty") || text.includes("No")) {
|
||||
resultMessage = text
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tool execution and capture results
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
const text = message.text || ""
|
||||
if (text.includes("list_code_definition_names")) {
|
||||
toolExecuted = true
|
||||
|
||||
// Extract tool results from the request
|
||||
try {
|
||||
const jsonMatch = text.match(/\{"request":".*?"\}/)
|
||||
if (jsonMatch) {
|
||||
const requestData = JSON.parse(jsonMatch[0])
|
||||
if (requestData.request && requestData.request.includes("Result:")) {
|
||||
// Extract the result after "Result:"
|
||||
const resultMatch = requestData.request.match(/Result:\s*([\s\S]*?)(?:\n\n|$)/)
|
||||
if (resultMatch) {
|
||||
resultMessage = resultMatch[1].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_e) {
|
||||
// Failed to parse, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.on("message", messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const completionHandler = (completedTaskId: string) => {
|
||||
if (completedTaskId === taskId) {
|
||||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", completionHandler)
|
||||
|
||||
try {
|
||||
// Start task to list code definitions from empty file
|
||||
const testDirName = path.basename(testFiles.testDir)
|
||||
const fileName = path.basename(testFiles.emptyFile)
|
||||
const relativePath = path.join(testDirName, fileName)
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
},
|
||||
text: `Use the list_code_definition_names tool to analyze the file at path "${relativePath}". This text file exists in the workspace test directory but is empty.`,
|
||||
})
|
||||
|
||||
// Wait for task completion
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Verify results
|
||||
assert.strictEqual(toolExecuted, true, "list_code_definition_names tool should have been executed")
|
||||
assert.ok(resultMessage, "Should have received a result message")
|
||||
// The tool should handle empty files gracefully, either returning "No definitions found" or similar
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", completionHandler)
|
||||
}
|
||||
})
|
||||
|
||||
test("Should list code definitions from nested directory file", async function () {
|
||||
this.timeout(90_000)
|
||||
const api = globalThis.api
|
||||
const messages: ClineMessage[] = []
|
||||
let _taskCompleted = false
|
||||
let toolExecuted = false
|
||||
let definitionsFound: string | null = null
|
||||
let taskId: string
|
||||
|
||||
// Listen for messages
|
||||
const messageHandler = ({ message }: { message: ClineMessage }) => {
|
||||
messages.push(message)
|
||||
|
||||
// Check for tool execution
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
const text = message.text || ""
|
||||
if (text.includes("list_code_definition_names")) {
|
||||
toolExecuted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for completion result or text messages with definitions
|
||||
if (message.type === "say" && (message.say === "completion_result" || message.say === "text")) {
|
||||
const result = message.text || ""
|
||||
if (
|
||||
result.includes("NestedService") ||
|
||||
result.includes("NestedConfig") ||
|
||||
result.includes("processNestedData") ||
|
||||
result.includes("definition")
|
||||
) {
|
||||
definitionsFound = result
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tool execution and capture results
|
||||
if (message.type === "say" && message.say === "api_req_started") {
|
||||
const text = message.text || ""
|
||||
if (text.includes("list_code_definition_names")) {
|
||||
toolExecuted = true
|
||||
|
||||
// Extract tool results from the request
|
||||
try {
|
||||
const jsonMatch = text.match(/\{"request":".*?"\}/)
|
||||
if (jsonMatch) {
|
||||
const requestData = JSON.parse(jsonMatch[0])
|
||||
if (requestData.request && requestData.request.includes("Result:")) {
|
||||
// Extract the result after "Result:"
|
||||
const resultMatch = requestData.request.match(/Result:\s*([\s\S]*?)(?:\n\n|$)/)
|
||||
if (resultMatch) {
|
||||
definitionsFound = resultMatch[1].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_e) {
|
||||
// Failed to parse, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.on("message", messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const completionHandler = (completedTaskId: string) => {
|
||||
if (completedTaskId === taskId) {
|
||||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", completionHandler)
|
||||
|
||||
try {
|
||||
// Start task to list code definitions from nested file
|
||||
const relativePath = path.relative(workspaceDir, testFiles.nestedTsFile)
|
||||
taskId = await api.startNewTask({
|
||||
configuration: {
|
||||
mode: "code",
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
},
|
||||
text: `Use the list_code_definition_names tool to analyze the file at path "${relativePath}". This TypeScript file exists in a nested directory within the workspace test directory and contains a class (NestedService), an interface (NestedConfig), and a function (processNestedData).`,
|
||||
})
|
||||
|
||||
// Wait for task completion
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Verify results
|
||||
assert.strictEqual(toolExecuted, true, "list_code_definition_names tool should have been executed")
|
||||
assert.ok(definitionsFound, "Should have found code definitions")
|
||||
|
||||
// Verify specific definitions are found
|
||||
if (definitionsFound) {
|
||||
const defs = definitionsFound as string
|
||||
assert.ok(defs.includes("NestedService"), "Should find NestedService class")
|
||||
assert.ok(defs.includes("NestedConfig"), "Should find NestedConfig interface")
|
||||
assert.ok(defs.includes("processNestedData"), "Should find processNestedData function")
|
||||
}
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", completionHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue