Merge pull request #691 from RooVetGit/cte/integration-tests

This commit is contained in:
Chris Estreich
2025-01-31 16:51:31 -08:00
committed by GitHub
14 changed files with 428 additions and 347 deletions

1
.env.integration.example Normal file
View File

@@ -0,0 +1 @@
OPENROUTER_API_KEY=sk-or-v1-...

View File

@@ -1,6 +1,7 @@
name: Code QA Roo Code name: Code QA Roo Code
on: on:
workflow_dispatch:
push: push:
branches: [main] branches: [main]
pull_request: pull_request:
@@ -13,33 +14,55 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '18'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
run: npm run install:all run: npm run install:all
- name: Compile
- name: Compile TypeScript
run: npm run compile run: npm run compile
- name: Check types
run: npm run check-types
- name: Lint
run: npm run lint
unit-test: unit-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '18'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
run: npm run install:all run: npm run install:all
- name: Run unit tests - name: Run unit tests
run: npm test run: npm test
integration-test:
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Create env.integration file
run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.integration
- name: Check env.integration file
run: cat .env.integration
- name: Install dependencies
run: npm run install:all
- run: xvfb-run -a npm run test:integration
if: runner.os == 'Linux'
- run: npm run test:integration
if: runner.os != 'Linux'

6
.gitignore vendored
View File

@@ -1,5 +1,6 @@
out
dist dist
out
out-integration
node_modules node_modules
coverage/ coverage/
@@ -18,3 +19,6 @@ roo-cline-*.vsix
# Docs # Docs
docs/_site/ docs/_site/
# Dotenv
.env.integration

View File

@@ -6,4 +6,7 @@ if [ "$branch" = "main" ]; then
fi fi
npx lint-staged npx lint-staged
npm run compile npm run compile
npm run lint
npm run check-types

View File

@@ -1,11 +1,16 @@
/**
* See: https://code.visualstudio.com/api/working-with-extensions/testing-extension
*/
import { defineConfig } from '@vscode/test-cli'; import { defineConfig } from '@vscode/test-cli';
export default defineConfig({ export default defineConfig({
files: 'src/test/extension.test.ts', label: 'integrationTest',
files: 'out-integration/test/**/*.test.js',
workspaceFolder: '.', workspaceFolder: '.',
mocha: { mocha: {
ui: 'tdd',
timeout: 60000, timeout: 60000,
ui: 'tdd'
}, },
launchArgs: [ launchArgs: [
'--enable-proposed-api=RooVeterinaryInc.roo-cline', '--enable-proposed-api=RooVeterinaryInc.roo-cline',

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1737569578,
"narHash": "sha256-6qY0pk2QmUtBT9Mywdvif0i/CLVgpCjMUn6g9vB+f3M=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "47addd76727f42d351590c905d9d1905ca895b82",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-24.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

33
flake.nix Normal file
View File

@@ -0,0 +1,33 @@
{
description = "Roo Code development environment";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
};
outputs = { self, nixpkgs, ... }: let
systems = [ "aarch64-darwin" "x86_64-linux" ];
forAllSystems = nixpkgs.lib.genAttrs systems;
mkDevShell = system: let
pkgs = import nixpkgs { inherit system; };
in pkgs.mkShell {
name = "roo-code";
packages = with pkgs; [
zsh
nodejs_18
corepack_18
];
shellHook = ''
exec zsh
'';
};
in {
devShells = forAllSystems (system: {
default = mkDevShell system;
});
};
}

176
package-lock.json generated
View File

@@ -55,6 +55,7 @@
"devDependencies": { "devDependencies": {
"@changesets/cli": "^2.27.10", "@changesets/cli": "^2.27.10",
"@changesets/types": "^6.0.0", "@changesets/types": "^6.0.0",
"@dotenvx/dotenvx": "^1.34.0",
"@types/diff": "^5.2.1", "@types/diff": "^5.2.1",
"@types/diff-match-patch": "^1.0.36", "@types/diff-match-patch": "^1.0.36",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
@@ -65,7 +66,6 @@
"@typescript-eslint/parser": "^7.11.0", "@typescript-eslint/parser": "^7.11.0",
"@vscode/test-cli": "^0.0.9", "@vscode/test-cli": "^0.0.9",
"@vscode/test-electron": "^2.4.0", "@vscode/test-electron": "^2.4.0",
"dotenv": "^16.4.7",
"esbuild": "^0.24.0", "esbuild": "^0.24.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"husky": "^9.1.7", "husky": "^9.1.7",
@@ -3030,6 +3030,110 @@
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
} }
}, },
"node_modules/@dotenvx/dotenvx": {
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.34.0.tgz",
"integrity": "sha512-+Dp/xaI3IZ4eKv+b2vg4V89VnqLKbmJ7UZ7unnZxMu9SNLOSc2jYaXey1YHCJM+67T0pOr2Gbej3TewnuoqTWQ==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"commander": "^11.1.0",
"dotenv": "^16.4.5",
"eciesjs": "^0.4.10",
"execa": "^5.1.1",
"fdir": "^6.2.0",
"ignore": "^5.3.0",
"object-treeify": "1.1.33",
"picomatch": "^4.0.2",
"which": "^4.0.0"
},
"bin": {
"dotenvx": "src/cli/dotenvx.js",
"git-dotenvx": "src/cli/dotenvx.js"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/@dotenvx/dotenvx/node_modules/commander": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/@dotenvx/dotenvx/node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/@dotenvx/dotenvx/node_modules/isexe": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16"
}
},
"node_modules/@dotenvx/dotenvx/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@dotenvx/dotenvx/node_modules/which": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^3.1.1"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
"node": "^16.13.0 || >=18.0.0"
}
},
"node_modules/@ecies/ciphers": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.2.tgz",
"integrity": "sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==",
"dev": true,
"license": "MIT",
"engines": {
"bun": ">=1",
"deno": ">=2",
"node": ">=16"
},
"peerDependencies": {
"@noble/ciphers": "^1.0.0"
}
},
"node_modules/@esbuild/darwin-arm64": { "node_modules/@esbuild/darwin-arm64": {
"version": "0.24.0", "version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
@@ -3964,6 +4068,48 @@
"zod": "^3.23.8" "zod": "^3.23.8"
} }
}, },
"node_modules/@noble/ciphers": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz",
"integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/curves": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz",
"integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.7.1"
},
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz",
"integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -7772,6 +7918,24 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"node_modules/eciesjs": {
"version": "0.4.13",
"resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.13.tgz",
"integrity": "sha512-zBdtR4K+wbj10bWPpIOF9DW+eFYQu8miU5ypunh0t4Bvt83ZPlEWgT5Dq/0G6uwEXumZKjfb5BZxYUZQ2Hzn/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ecies/ciphers": "^0.2.2",
"@noble/ciphers": "^1.0.0",
"@noble/curves": "^1.6.0",
"@noble/hashes": "^1.5.0"
},
"engines": {
"bun": ">=1",
"deno": ">=2",
"node": ">=16"
}
},
"node_modules/eight-colors": { "node_modules/eight-colors": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/eight-colors/-/eight-colors-1.3.1.tgz", "resolved": "https://registry.npmjs.org/eight-colors/-/eight-colors-1.3.1.tgz",
@@ -12247,6 +12411,16 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/object-treeify": {
"version": "1.1.33",
"resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz",
"integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/object.assign": { "node_modules/object.assign": {
"version": "4.1.5", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",

View File

@@ -221,16 +221,16 @@
"build:webview": "cd webview-ui && npm run build", "build:webview": "cd webview-ui && npm run build",
"changeset": "changeset", "changeset": "changeset",
"check-types": "tsc --noEmit", "check-types": "tsc --noEmit",
"compile": "npm run check-types && npm run lint && node esbuild.js", "compile": "tsc -p . --outDir out && node esbuild.js",
"compile-tests": "tsc -p . --outDir out", "compile:integration": "tsc -p tsconfig.integration.json",
"install:all": "npm install && cd webview-ui && npm install", "install:all": "npm install && cd webview-ui && npm install",
"lint": "eslint src --ext ts && npm run lint --prefix webview-ui", "lint": "eslint src --ext ts && npm run lint --prefix webview-ui",
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production", "package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
"pretest": "npm run compile-tests && npm run compile && npm run lint", "pretest": "npm run compile && npm run compile:integration",
"dev": "cd webview-ui && npm run dev", "dev": "cd webview-ui && npm run dev",
"test": "jest && npm run test:webview", "test": "jest && npm run test:webview",
"test:webview": "cd webview-ui && npm run test", "test:webview": "cd webview-ui && npm run test",
"test:extension": "vscode-test", "test:integration": "npm run build && npm run compile:integration && npx dotenvx run -f .env.integration -- vscode-test",
"prepare": "husky", "prepare": "husky",
"publish:marketplace": "vsce publish && ovsx publish", "publish:marketplace": "vsce publish && ovsx publish",
"publish": "npm run build && changeset publish && npm install --package-lock-only", "publish": "npm run build && changeset publish && npm install --package-lock-only",
@@ -245,6 +245,7 @@
"devDependencies": { "devDependencies": {
"@changesets/cli": "^2.27.10", "@changesets/cli": "^2.27.10",
"@changesets/types": "^6.0.0", "@changesets/types": "^6.0.0",
"@dotenvx/dotenvx": "^1.34.0",
"@types/diff": "^5.2.1", "@types/diff": "^5.2.1",
"@types/diff-match-patch": "^1.0.36", "@types/diff-match-patch": "^1.0.36",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
@@ -255,7 +256,6 @@
"@typescript-eslint/parser": "^7.11.0", "@typescript-eslint/parser": "^7.11.0",
"@vscode/test-cli": "^0.0.9", "@vscode/test-cli": "^0.0.9",
"@vscode/test-electron": "^2.4.0", "@vscode/test-electron": "^2.4.0",
"dotenv": "^16.4.7",
"esbuild": "^0.24.0", "esbuild": "^0.24.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"husky": "^9.1.7", "husky": "^9.1.7",

View File

@@ -19,15 +19,7 @@ import { findLast } from "../../shared/array"
import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage" import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
import { HistoryItem } from "../../shared/HistoryItem" import { HistoryItem } from "../../shared/HistoryItem"
import { WebviewMessage } from "../../shared/WebviewMessage" import { WebviewMessage } from "../../shared/WebviewMessage"
import { import { Mode, CustomModePrompts, PromptComponent, defaultModeSlug } from "../../shared/modes"
Mode,
modes,
CustomModePrompts,
PromptComponent,
ModeConfig,
defaultModeSlug,
getModeBySlug,
} from "../../shared/modes"
import { SYSTEM_PROMPT } from "../prompts/system" import { SYSTEM_PROMPT } from "../prompts/system"
import { fileExistsAtPath } from "../../utils/fs" import { fileExistsAtPath } from "../../utils/fs"
import { Cline } from "../Cline" import { Cline } from "../Cline"
@@ -37,7 +29,7 @@ import { getUri } from "./getUri"
import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound" import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
import { checkExistKey } from "../../shared/checkExistApiConfig" import { checkExistKey } from "../../shared/checkExistApiConfig"
import { singleCompletionHandler } from "../../utils/single-completion-handler" import { singleCompletionHandler } from "../../utils/single-completion-handler"
import { getCommitInfo, searchCommits, getWorkingState } from "../../utils/git" import { searchCommits } from "../../utils/git"
import { ConfigManager } from "../config/ConfigManager" import { ConfigManager } from "../config/ConfigManager"
import { CustomModesManager } from "../config/CustomModesManager" import { CustomModesManager } from "../config/CustomModesManager"
import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments" import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
@@ -140,6 +132,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
private static activeInstances: Set<ClineProvider> = new Set() private static activeInstances: Set<ClineProvider> = new Set()
private disposables: vscode.Disposable[] = [] private disposables: vscode.Disposable[] = []
private view?: vscode.WebviewView | vscode.WebviewPanel private view?: vscode.WebviewView | vscode.WebviewPanel
private isViewLaunched = false
private cline?: Cline private cline?: Cline
private workspaceTracker?: WorkspaceTracker private workspaceTracker?: WorkspaceTracker
mcpHub?: McpHub mcpHub?: McpHub
@@ -652,6 +645,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
), ),
) )
this.isViewLaunched = true
break break
case "newTask": case "newTask":
// Code that should run in response to the hello message command // Code that should run in response to the hello message command
@@ -2422,7 +2416,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
// secrets // secrets
private async storeSecret(key: SecretKey, value?: string) { public async storeSecret(key: SecretKey, value?: string) {
if (value) { if (value) {
await this.context.secrets.store(key, value) await this.context.secrets.store(key, value)
} else { } else {
@@ -2476,4 +2470,14 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.postStateToWebview() await this.postStateToWebview()
await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
} }
// integration tests
get viewLaunched() {
return this.isViewLaunched
}
get messages() {
return this.cline?.clineMessages || []
}
} }

View File

@@ -1,121 +1,18 @@
const assert = require("assert") import * as assert from "assert"
const vscode = require("vscode") import * as vscode from "vscode"
const path = require("path")
const fs = require("fs")
const dotenv = require("dotenv")
// Load test environment variables suite("Roo Code Extension", () => {
const testEnvPath = path.join(__dirname, ".test_env") test("OPENROUTER_API_KEY environment variable is set", () => {
dotenv.config({ path: testEnvPath }) if (!process.env.OPENROUTER_API_KEY) {
assert.fail("OPENROUTER_API_KEY environment variable is not set")
suite("Roo Code Extension Test Suite", () => {
vscode.window.showInformationMessage("Starting Roo Code extension tests.")
test("Extension should be present", () => {
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
assert.notStrictEqual(extension, undefined)
})
test("Extension should activate", async () => {
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
if (!extension) {
assert.fail("Extension not found")
} }
await extension.activate()
assert.strictEqual(extension.isActive, true)
})
test("OpenRouter API key and models should be configured correctly", function (done) {
// @ts-ignore
this.timeout(60000) // Increase timeout to 60s for network requests
;(async () => {
try {
// Get extension instance
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
if (!extension) {
done(new Error("Extension not found"))
return
}
// Verify API key is set and valid
const apiKey = process.env.OPEN_ROUTER_API_KEY
if (!apiKey) {
done(new Error("OPEN_ROUTER_API_KEY environment variable is not set"))
return
}
if (!apiKey.startsWith("sk-or-v1-")) {
done(new Error("OpenRouter API key should have correct format"))
return
}
// Activate extension and get provider
const api = await extension.activate()
if (!api) {
done(new Error("Extension API not found"))
return
}
// Get the provider from the extension's exports
const provider = api.sidebarProvider
if (!provider) {
done(new Error("Provider not found"))
return
}
// Set up the API configuration
await provider.updateGlobalState("apiProvider", "openrouter")
await provider.storeSecret("openRouterApiKey", apiKey)
// Set up timeout to fail test if models don't load
const timeout = setTimeout(() => {
done(new Error("Timeout waiting for models to load"))
}, 30000)
// Wait for models to be loaded
const checkModels = setInterval(async () => {
try {
const models = await provider.readOpenRouterModels()
if (!models) {
return
}
clearInterval(checkModels)
clearTimeout(timeout)
// Verify expected Claude models are available
const expectedModels = [
"anthropic/claude-3.5-sonnet:beta",
"anthropic/claude-3-sonnet:beta",
"anthropic/claude-3.5-sonnet",
"anthropic/claude-3.5-sonnet-20240620",
"anthropic/claude-3.5-sonnet-20240620:beta",
"anthropic/claude-3.5-haiku:beta",
]
for (const modelId of expectedModels) {
assert.strictEqual(modelId in models, true, `Model ${modelId} should be available`)
}
done()
} catch (error) {
clearInterval(checkModels)
clearTimeout(timeout)
done(error)
}
}, 1000)
// Trigger model loading
await provider.refreshOpenRouterModels()
} catch (error) {
done(error)
}
})()
}) })
test("Commands should be registered", async () => { test("Commands should be registered", async () => {
const commands = await vscode.commands.getCommands(true) const timeout = 10 * 1_000
const interval = 1_000
const startTime = Date.now()
// Test core commands are registered
const expectedCommands = [ const expectedCommands = [
"roo-cline.plusButtonClicked", "roo-cline.plusButtonClicked",
"roo-cline.mcpButtonClicked", "roo-cline.mcpButtonClicked",
@@ -128,204 +25,39 @@ suite("Roo Code Extension Test Suite", () => {
"roo-cline.improveCode", "roo-cline.improveCode",
] ]
while (Date.now() - startTime < timeout) {
const commands = await vscode.commands.getCommands(true)
const missingCommands = []
for (const cmd of expectedCommands) {
if (!commands.includes(cmd)) {
missingCommands.push(cmd)
}
}
if (missingCommands.length === 0) {
break
}
await new Promise((resolve) => setTimeout(resolve, interval))
}
const commands = await vscode.commands.getCommands(true)
for (const cmd of expectedCommands) { for (const cmd of expectedCommands) {
assert.strictEqual(commands.includes(cmd), true, `Command ${cmd} should be registered`) assert.ok(commands.includes(cmd), `Command ${cmd} should be registered`)
} }
}) })
test("Views should be registered", () => { test("Webview panel can be created", () => {
const view = vscode.window.createWebviewPanel( const view = vscode.window.createWebviewPanel(
"roo-cline.SidebarProvider", "roo-cline.SidebarProvider",
"Roo Code", "Roo Code",
vscode.ViewColumn.One, vscode.ViewColumn.One,
{}, {},
) )
assert.notStrictEqual(view, undefined)
assert.ok(view, "Failed to create webview panel")
view.dispose() view.dispose()
}) })
test("Should handle prompt and response correctly", async function () {
// @ts-ignore
this.timeout(60000) // Increase timeout for API request
const timeout = 30000
const interval = 1000
// Get extension instance
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
if (!extension) {
assert.fail("Extension not found")
return
}
// Activate extension and get API
const api = await extension.activate()
if (!api) {
assert.fail("Extension API not found")
return
}
// Get provider
const provider = api.sidebarProvider
if (!provider) {
assert.fail("Provider not found")
return
}
// Set up API configuration
await provider.updateGlobalState("apiProvider", "openrouter")
await provider.updateGlobalState("openRouterModelId", "anthropic/claude-3.5-sonnet")
const apiKey = process.env.OPEN_ROUTER_API_KEY
if (!apiKey) {
assert.fail("OPEN_ROUTER_API_KEY environment variable is not set")
return
}
await provider.storeSecret("openRouterApiKey", apiKey)
// Create webview panel with development options
const extensionUri = extension.extensionUri
const panel = vscode.window.createWebviewPanel("roo-cline.SidebarProvider", "Roo Code", vscode.ViewColumn.One, {
enableScripts: true,
enableCommandUris: true,
retainContextWhenHidden: true,
localResourceRoots: [extensionUri],
})
try {
// Initialize webview with development context
panel.webview.options = {
enableScripts: true,
enableCommandUris: true,
localResourceRoots: [extensionUri],
}
// Initialize provider with panel
provider.resolveWebviewView(panel)
// Set up message tracking
let webviewReady = false
let messagesReceived = false
const originalPostMessage = provider.postMessageToWebview.bind(provider)
// @ts-ignore
provider.postMessageToWebview = async (message) => {
if (message.type === "state") {
webviewReady = true
console.log("Webview state received:", message)
if (message.state?.clineMessages?.length > 0) {
messagesReceived = true
console.log("Messages in state:", message.state.clineMessages)
}
}
await originalPostMessage(message)
}
// Wait for webview to launch and receive initial state
let startTime = Date.now()
while (Date.now() - startTime < timeout) {
if (webviewReady) {
// Wait an additional second for webview to fully initialize
await new Promise((resolve) => setTimeout(resolve, 1000))
break
}
await new Promise((resolve) => setTimeout(resolve, interval))
}
if (!webviewReady) {
throw new Error("Timeout waiting for webview to be ready")
}
// Send webviewDidLaunch to initialize chat
await provider.postMessageToWebview({ type: "webviewDidLaunch" })
console.log("Sent webviewDidLaunch")
// Wait for webview to fully initialize
await new Promise((resolve) => setTimeout(resolve, 2000))
// Restore original postMessage
provider.postMessageToWebview = originalPostMessage
// Wait for OpenRouter models to be fully loaded
startTime = Date.now()
while (Date.now() - startTime < timeout) {
const models = await provider.readOpenRouterModels()
if (models && Object.keys(models).length > 0) {
console.log("OpenRouter models loaded")
break
}
await new Promise((resolve) => setTimeout(resolve, interval))
}
// Send prompt
const prompt = "Hello world, what is your name?"
console.log("Sending prompt:", prompt)
// Start task
try {
await api.startNewTask(prompt)
console.log("Task started")
} catch (error) {
console.error("Error starting task:", error)
throw error
}
// Wait for task to appear in history with tokens
startTime = Date.now()
while (Date.now() - startTime < timeout) {
const state = await provider.getState()
const task = state.taskHistory?.[0]
if (task && task.tokensOut > 0) {
console.log("Task completed with tokens:", task)
break
}
await new Promise((resolve) => setTimeout(resolve, interval))
}
// Wait for messages to be processed
startTime = Date.now()
let responseReceived = false
while (Date.now() - startTime < timeout) {
// Check provider.clineMessages
const messages = provider.clineMessages
if (messages && messages.length > 0) {
console.log("Provider messages:", JSON.stringify(messages, null, 2))
// @ts-ignore
const hasResponse = messages.some(
(m: { type: string; text: string }) =>
m.type === "say" && m.text && m.text.toLowerCase().includes("cline"),
)
if (hasResponse) {
console.log('Found response containing "Cline" in provider messages')
responseReceived = true
break
}
}
// Check provider.cline.clineMessages
const clineMessages = provider.cline?.clineMessages
if (clineMessages && clineMessages.length > 0) {
console.log("Cline messages:", JSON.stringify(clineMessages, null, 2))
// @ts-ignore
const hasResponse = clineMessages.some(
(m: { type: string; text: string }) =>
m.type === "say" && m.text && m.text.toLowerCase().includes("cline"),
)
if (hasResponse) {
console.log('Found response containing "Cline" in cline messages')
responseReceived = true
break
}
}
await new Promise((resolve) => setTimeout(resolve, interval))
}
if (!responseReceived) {
console.log("Final provider state:", await provider.getState())
console.log("Final cline messages:", provider.cline?.clineMessages)
throw new Error('Did not receive expected response containing "Cline"')
}
} finally {
panel.dispose()
}
})
}) })

77
src/test/task.test.ts Normal file
View File

@@ -0,0 +1,77 @@
import * as assert from "assert"
import * as vscode from "vscode"
import { ClineAPI } from "../exports/cline"
import { ClineProvider } from "../core/webview/ClineProvider"
suite("Roo Code Task", () => {
test("Should handle prompt and response correctly", async function () {
const timeout = 30000
const interval = 1000
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
if (!extension) {
assert.fail("Extension not found")
}
const api: ClineAPI = await extension.activate()
const provider = api.sidebarProvider as ClineProvider
await provider.updateGlobalState("apiProvider", "openrouter")
await provider.updateGlobalState("openRouterModelId", "anthropic/claude-3.5-sonnet")
await provider.storeSecret("openRouterApiKey", process.env.OPENROUTER_API_KEY || "sk-or-v1-fake-api-key")
// Create webview panel with development options.
const panel = vscode.window.createWebviewPanel("roo-cline.SidebarProvider", "Roo Code", vscode.ViewColumn.One, {
enableScripts: true,
enableCommandUris: true,
retainContextWhenHidden: true,
localResourceRoots: [extension.extensionUri],
})
try {
// Initialize provider with panel.
provider.resolveWebviewView(panel)
// Wait for webview to launch.
let startTime = Date.now()
while (Date.now() - startTime < timeout) {
if (provider.viewLaunched) {
break
}
await new Promise((resolve) => setTimeout(resolve, interval))
}
await api.startNewTask("Hello world, what is your name? Respond with 'My name is ...'")
// Wait for task to appear in history with tokens.
startTime = Date.now()
while (Date.now() - startTime < timeout) {
const state = await provider.getState()
const task = state.taskHistory?.[0]
if (task && task.tokensOut > 0) {
break
}
await new Promise((resolve) => setTimeout(resolve, interval))
}
if (provider.messages.length === 0) {
assert.fail("No messages received")
}
// console.log("Provider messages:", JSON.stringify(provider.messages, null, 2))
assert.ok(
provider.messages.some(({ type, text }) => type === "say" && text?.includes("My name is Roo")),
"Did not receive expected response containing 'My name is Roo'",
)
} finally {
panel.dispose()
}
})
})

View File

@@ -1,19 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"lib": ["ES2020"],
"sourceMap": true,
"rootDir": "../..",
"strict": false,
"noImplicitAny": false,
"noImplicitThis": false,
"alwaysStrict": false,
"skipLibCheck": true,
"baseUrl": "../..",
"paths": {
"*": ["*", "src/*"]
}
},
"exclude": ["node_modules", ".vscode-test"]
}

17
tsconfig.integration.json Normal file
View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node",
"esModuleInterop": true,
"target": "ES2022",
"lib": ["ES2022", "ESNext.Disposable", "DOM"],
"sourceMap": true,
"strict": true,
"skipLibCheck": true,
"useUnknownInCatchVariables": false,
"rootDir": "src",
"outDir": "out-integration"
},
"include": ["**/*.ts"],
"exclude": [".vscode-test", "benchmark", "dist", "**/node_modules/**", "out", "out-integration", "webview-ui"]
}