Skip to content

Commit cfd697e

Browse files
authored
Merge branch 'main' into issue-8-monolith-refactor
2 parents cec5766 + ce37c9e commit cfd697e

42 files changed

Lines changed: 829 additions & 46 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/code-qa.yml

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,34 @@ jobs:
4949
include:
5050
- os: ubuntu-latest
5151
name: ubuntu-latest
52+
codecov-flag: ubuntu
5253
- os: windows-latest
5354
name: windows-latest
55+
codecov-flag: windows
5456
steps:
5557
- name: Checkout code
5658
uses: actions/checkout@v4
5759
- name: Setup Node.js and pnpm
5860
uses: ./.github/actions/setup-node-pnpm
59-
- name: Run unit tests
60-
run: pnpm test
61+
- name: Cache Turbo
62+
uses: actions/cache@v4
63+
with:
64+
path: .turbo/cache
65+
key: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}
66+
restore-keys: |
67+
${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}-
68+
${{ runner.os }}-turbo-
69+
- name: Run unit tests with coverage
70+
run: pnpm test:coverage
71+
- name: Upload coverage to Codecov
72+
uses: codecov/codecov-action@v4
73+
with:
74+
files: >-
75+
src/coverage/lcov.info,
76+
webview-ui/coverage/lcov.info,
77+
packages/core/coverage/lcov.info,
78+
packages/cloud/coverage/lcov.info,
79+
packages/telemetry/coverage/lcov.info,
80+
apps/cli/coverage/lcov.info
81+
flags: ${{ matrix.codecov-flag }}
82+
token: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/e2e.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: E2E Tests (Mocked)
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
types: [opened, reopened, ready_for_review, synchronize]
7+
branches: [main]
8+
paths:
9+
- "src/**"
10+
- "webview-ui/**"
11+
- "apps/vscode-e2e/**"
12+
- "packages/core/**"
13+
- "package.json"
14+
- "pnpm-lock.yaml"
15+
- "turbo.json"
16+
- ".github/actions/setup-node-pnpm/**"
17+
18+
jobs:
19+
e2e-mock:
20+
runs-on: ubuntu-latest
21+
timeout-minutes: 30
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@v4
25+
- name: Setup Node.js and pnpm
26+
uses: ./.github/actions/setup-node-pnpm
27+
- name: Install xvfb
28+
run: sudo apt-get install -y xvfb
29+
- name: Run mocked E2E tests
30+
run: xvfb-run -a pnpm --filter @roo-code/vscode-e2e test:ci:mock

apps/cli/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"lint": "eslint src --ext .ts --max-warnings=0",
1414
"check-types": "tsc --noEmit",
1515
"test": "vitest run",
16+
"test:coverage": "vitest run --coverage",
1617
"test:integration": "tsx scripts/integration/run.ts",
1718
"build": "tsup",
1819
"build:extension": "pnpm --filter roo-cline bundle",
@@ -45,6 +46,7 @@
4546
"ink-testing-library": "^4.0.0",
4647
"rimraf": "^6.0.1",
4748
"tsup": "^8.4.0",
49+
"@vitest/coverage-v8": "^3.2.3",
4850
"vitest": "^3.2.3"
4951
}
5052
}

apps/cli/vitest.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,10 @@ export default defineConfig({
1313
watch: false,
1414
testTimeout: 120_000, // 2m for integration tests.
1515
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
16+
coverage: {
17+
provider: "v8",
18+
reporter: ["text", "lcov"],
19+
exclude: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/vitest.config.ts"],
20+
},
1621
},
1722
})

apps/vscode-e2e/AGENTS.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# E2E Test Fixture Workflow
2+
3+
E2E tests run against `@copilotkit/aimock` (`LLMock`) — a local HTTP server that replays recorded LLM responses. This makes tests free, deterministic, and CI-friendly.
4+
5+
## How aimock matching works
6+
7+
Fixtures are matched by **substring**: `incoming_last_user_message.includes(fixture.match.userMessage)`. A fixture fires if its match string appears _anywhere_ in the last user message of the API request.
8+
9+
**Critical**: the last user message always contains `<environment_details>` with the current time. Never use a match string that includes a timestamp — it will stop matching on the next run.
10+
11+
Record mode uses **record-on-miss**: if an existing fixture already matches a request, aimock serves it and does **not** re-record. Only unmatched requests are proxied to the real API and saved as `openai-*.json` files.
12+
13+
## Adding a fixture for a new test
14+
15+
1. Write the test in `src/suite/`. Use short, stable, unique text in the task prompt.
16+
17+
2. Clear any stale auto-recorded files first (they accumulate across record runs):
18+
19+
```sh
20+
git clean -fx apps/vscode-e2e/fixtures/
21+
```
22+
23+
The `-x` flag is required because `openai-*.json` files are gitignored — `git clean -f` alone silently skips them.
24+
25+
3. Record fixtures (requires an OpenRouter API key with credits):
26+
27+
```sh
28+
OPENROUTER_API_KEY=<key> pnpm --filter @roo-code/vscode-e2e test:record
29+
```
30+
31+
This proxies unmatched requests to OpenRouter and writes `fixtures/openai-*.json`. Background
32+
calls from the extension will also be recorded here — that's expected, ignore them.
33+
34+
4. Find the auto-recorded file for your test:
35+
36+
```sh
37+
grep -l "your unique prompt text" apps/vscode-e2e/fixtures/openai-*.json
38+
```
39+
40+
5. Inspect it to find the `response` block (tool calls the LLM made).
41+
42+
6. Create a named fixture file, e.g. `fixtures/my-feature.json`, with a **short stable match string**:
43+
44+
```json
45+
{
46+
"fixtures": [
47+
{
48+
"match": { "userMessage": "your unique prompt text" },
49+
"response": {
50+
"toolCalls": [
51+
{ "name": "attempt_completion", "arguments": "{\"result\":\"...\"}", "id": "call_001" }
52+
]
53+
}
54+
}
55+
]
56+
}
57+
```
58+
59+
The match string should be unique enough to identify this request but contain **no timestamps, file paths, or environment details**.
60+
61+
7. Delete the `openai-*.json` files — they're gitignored and can't be replayed.
62+
63+
8. Verify in mock mode (no API key needed):
64+
```sh
65+
pnpm --filter @roo-code/vscode-e2e test:ci:mock
66+
```
67+
68+
## Multi-turn tests
69+
70+
If the LLM calls a tool first (e.g. `read_file`) and then calls `attempt_completion` after seeing the result, you need two fixtures:
71+
72+
- **Turn 1**: match on the task prompt → respond with the tool call
73+
- **Turn 2**: match on a stable part of the tool _result_ → respond with `attempt_completion`
74+
75+
The tool result is provided by the extension (not the mock), so its content is deterministic if test files have stable names. Use a stable substring from the tool result as the turn-2 match string.
76+
77+
## 404 errors in logs are expected
78+
79+
Background API calls from the extension (usage collection, initialization) hit aimock with no matching fixture and return 404. These do **not** affect test results — the tests still pass. You'll see `[OpenRouter] API error: { message: '404 No fixture matched' }` in the output; this is normal.
80+
81+
## Running tests
82+
83+
| Command | Purpose |
84+
| ------------------------------------------------------------------------- | ------------------------------------------------------------------ |
85+
| `pnpm --filter @roo-code/vscode-e2e test:ci:mock` | Replay mode — no API key needed, uses fixtures |
86+
| `OPENROUTER_API_KEY=<key> pnpm --filter @roo-code/vscode-e2e test:record` | Record mode — proxies to real API, writes `openai-*.json` |
87+
| `OPENROUTER_API_KEY=<key> pnpm --filter @roo-code/vscode-e2e test:ci` | Real-API mode — runs against live OpenRouter (for drift detection) |
88+
89+
## Programmatic fixtures (regex matching)
90+
91+
For requests that can't be matched by a stable substring (e.g. "starts with `<environment_details>` but not preceded by a user message"), add a programmatic fixture in `src/runTest.ts` using `mock.addFixture()` with a `RegExp` match. These are only available in replay mode and are not recorded.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Auto-recorded fixtures have timestamp-based match strings and never replay correctly.
2+
# Contributors should extract stable fixtures manually from these files, then delete them.
3+
openai-*.json

apps/vscode-e2e/fixtures/.gitkeep

Whitespace-only changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"userMessage": "Please show me an example of an unordered list with the following items: Apple, Banana, Orange"
6+
},
7+
"response": {
8+
"toolCalls": [
9+
{
10+
"name": "attempt_completion",
11+
"arguments": "{\"result\":\"Here is an unordered list:\\n- Apple\\n- Banana\\n- Orange\"}",
12+
"id": "call_markdown_unordered_001"
13+
}
14+
]
15+
}
16+
},
17+
{
18+
"match": {
19+
"userMessage": "Please show me a numbered list with three steps: First step, Second step, Third step"
20+
},
21+
"response": {
22+
"toolCalls": [
23+
{
24+
"name": "attempt_completion",
25+
"arguments": "{\"result\":\"Here is a numbered list:\\n1. First step\\n2. Second step\\n3. Third step\"}",
26+
"id": "call_markdown_ordered_001"
27+
}
28+
]
29+
}
30+
},
31+
{
32+
"match": {
33+
"userMessage": "Please create a nested list with 'Main item' having two sub-items: 'Sub-item A' and 'Sub-item B'"
34+
},
35+
"response": {
36+
"toolCalls": [
37+
{
38+
"name": "attempt_completion",
39+
"arguments": "{\"result\":\"Here is a nested list:\\n- Main item\\n - Sub-item A\\n - Sub-item B\"}",
40+
"id": "call_markdown_nested_001"
41+
}
42+
]
43+
}
44+
},
45+
{
46+
"match": {
47+
"userMessage": "Please create a list that has both numbered items and bullet points, mixing ordered and unordered lists"
48+
},
49+
"response": {
50+
"toolCalls": [
51+
{
52+
"name": "attempt_completion",
53+
"arguments": "{\"result\":\"Here is a mixed list:\\n1. First numbered item\\n- Bullet point A\\n- Bullet point B\\n2. Second numbered item\"}",
54+
"id": "call_markdown_mixed_001"
55+
}
56+
]
57+
}
58+
}
59+
]
60+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"userMessage": "Use the `switch_mode` tool to switch to ask mode."
6+
},
7+
"response": {
8+
"toolCalls": [
9+
{
10+
"name": "switch_mode",
11+
"arguments": "{\"mode_slug\":\"ask\",\"reason\":\"User requested to switch to ask mode.\"}",
12+
"id": "call_modes_switch_001"
13+
}
14+
]
15+
}
16+
}
17+
]
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"userMessage": "Hello world, what is your name? Respond with 'My name is ...'"
6+
},
7+
"response": {
8+
"toolCalls": [
9+
{
10+
"name": "attempt_completion",
11+
"arguments": "{\"result\":\"My name is Roo! I'm your AI coding assistant, here to help you with development tasks.\"}",
12+
"id": "call_task_hello_world_001"
13+
}
14+
]
15+
}
16+
}
17+
]
18+
}

0 commit comments

Comments
 (0)