Skip to content

Commit 2086121

Browse files
committed
test: reorganize .agents tests into unit and e2e, add nightly e2e workflow
1 parent 394c3d5 commit 2086121

File tree

10 files changed

+140
-40
lines changed

10 files changed

+140
-40
lines changed

.agents/__tests__/context-pruner.integration.test.ts renamed to .agents/e2e/context-pruner.e2e.test.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
type ToolMessage,
1111
type JSONValue,
1212
} from '@codebuff/sdk'
13-
1413
/**
1514
* Integration tests for the context-pruner agent.
1615
* These tests verify that context-pruner correctly prunes message history
@@ -58,10 +57,7 @@ describe('Context Pruner Agent Integration', () => {
5857
it(
5958
'should prune large message history and maintain tool-call/tool-result pairs',
6059
async () => {
61-
const apiKey = process.env[API_KEY_ENV_VAR]
62-
if (!apiKey) {
63-
throw new Error('API key not found')
64-
}
60+
const apiKey = process.env[API_KEY_ENV_VAR]!
6561

6662
// Create a test agent that spawns context-pruner and then does one more step
6763
const testAgent: AgentDefinition = {
@@ -196,10 +192,7 @@ Do not do anything else. Just spawn context-pruner and then report the result.`,
196192
it(
197193
'should prune context with small token limit and preserve tool pairs',
198194
async () => {
199-
const apiKey = process.env[API_KEY_ENV_VAR]
200-
if (!apiKey) {
201-
throw new Error('API key not found')
202-
}
195+
const apiKey = process.env[API_KEY_ENV_VAR]!
203196

204197
// Create a test agent that spawns context-pruner with very aggressive pruning
205198
const testAgent: AgentDefinition = {

.agents/__tests__/editor-best-of-n.integration.test.ts renamed to .agents/e2e/editor-best-of-n.e2e.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { PrintModeEvent } from '@codebuff/common/types/print-mode'
1414
* 4. Applies the chosen implementation
1515
*/
1616
describe('Editor Best-of-N Max Agent Integration', () => {
17-
it.skip(
17+
it(
1818
'should generate and select the best implementation for a simple edit',
1919
async () => {
2020
const apiKey = process.env[API_KEY_ENV_VAR]

.agents/__tests__/file-explorer.integration.test.ts renamed to .agents/e2e/file-explorer.e2e.test.ts

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { describe, expect, it } from 'bun:test'
44
import { CodebuffClient } from '@codebuff/sdk'
55
import filePickerDefinition from '../file-explorer/file-picker'
66
import fileListerDefinition from '../file-explorer/file-lister'
7-
87
import type { PrintModeEvent } from '@codebuff/common/types/print-mode'
98

109
/**
@@ -22,10 +21,7 @@ describe('File Lister Agent Integration - read_subtree tool', () => {
2221
it(
2322
'should find relevant files using read_subtree tool',
2423
async () => {
25-
const apiKey = process.env[API_KEY_ENV_VAR]
26-
if (!apiKey) {
27-
throw new Error('API key not found')
28-
}
24+
const apiKey = process.env[API_KEY_ENV_VAR]!
2925

3026
// Create mock project files that the file-lister should be able to find
3127
const projectFiles: Record<string, string> = {
@@ -142,10 +138,7 @@ export interface User {
142138
it(
143139
'should use the file tree from session state',
144140
async () => {
145-
const apiKey = process.env[API_KEY_ENV_VAR]
146-
if (!apiKey) {
147-
throw new Error('API key not found')
148-
}
141+
const apiKey = process.env[API_KEY_ENV_VAR]!
149142

150143
// Create a different set of project files with a specific structure
151144
const projectFiles: Record<string, string> = {
@@ -196,10 +189,7 @@ export interface User {
196189
it(
197190
'should respect directories parameter',
198191
async () => {
199-
const apiKey = process.env[API_KEY_ENV_VAR]
200-
if (!apiKey) {
201-
throw new Error('API key not found')
202-
}
192+
const apiKey = process.env[API_KEY_ENV_VAR]!
203193

204194
// Create project with multiple top-level directories
205195
const projectFiles: Record<string, string> = {
@@ -261,10 +251,7 @@ describe('File Picker Agent Integration - spawn_agents tool', () => {
261251
it.skip(
262252
'should spawn file-lister subagent and find relevant files',
263253
async () => {
264-
const apiKey = process.env[API_KEY_ENV_VAR]
265-
if (!apiKey) {
266-
throw new Error('API key not found')
267-
}
254+
const apiKey = process.env[API_KEY_ENV_VAR]!
268255

269256
// Create mock project files
270257
const projectFiles: Record<string, string> = {

.agents/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"typecheck": "bun x tsc --noEmit -p tsconfig.json",
8-
"test": "bun test"
8+
"test": "bun test __tests__",
9+
"test:e2e": "bun test e2e"
910
}
1011
}

.github/workflows/ci.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,9 @@ jobs:
242242
command: |
243243
cd ${{ matrix.package }}
244244
if [ "${{ matrix.package }}" = ".agents" ]; then
245-
TEST_FILES=$(find __tests__ -name '*.integration.test.ts' 2>/dev/null | sort)
246-
if [ -n "$TEST_FILES" ]; then
247-
echo "$TEST_FILES" | xargs -I {} bun test --timeout=60000 {}
248-
else
249-
echo "No integration tests found in .agents"
250-
fi
245+
# .agents e2e tests are in e2e/ directory and require real services
246+
# They are skipped in CI - run locally with: bun run test:e2e
247+
echo "Skipping .agents e2e tests in CI (require real services)"
251248
else
252249
find src -name '*.integration.test.ts' | sort | xargs -I {} bun test --timeout=60000 {}
253250
fi
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Nightly Agents E2E Tests
2+
3+
on:
4+
schedule:
5+
# Run every day at 5:00 AM PT (12:00 UTC)
6+
- cron: '0 12 * * *'
7+
workflow_dispatch: # Allow manual triggering
8+
9+
jobs:
10+
agents-e2e-tests:
11+
runs-on: ubuntu-latest
12+
timeout-minutes: 30
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Bun
18+
uses: oven-sh/setup-bun@v2
19+
with:
20+
bun-version: '1.3.0'
21+
22+
- name: Cache dependencies
23+
uses: actions/cache@v4
24+
with:
25+
path: |
26+
node_modules
27+
*/node_modules
28+
packages/*/node_modules
29+
key: ${{ runner.os }}-deps-${{ hashFiles('**/bun.lock*') }}
30+
restore-keys: |
31+
${{ runner.os }}-deps-
32+
33+
- name: Install dependencies
34+
run: bun install --frozen-lockfile
35+
36+
- name: Set environment variables
37+
env:
38+
SECRETS_CONTEXT: ${{ toJSON(secrets) }}
39+
run: |
40+
VAR_NAMES=$(bun scripts/generate-ci-env.ts)
41+
echo "$SECRETS_CONTEXT" | jq -r --argjson vars "$VAR_NAMES" '
42+
to_entries | .[] | select(.key as $k | $vars | index($k)) | .key + "=" + .value
43+
' >> $GITHUB_ENV
44+
echo "CODEBUFF_GITHUB_ACTIONS=true" >> $GITHUB_ENV
45+
echo "NEXT_PUBLIC_CB_ENVIRONMENT=test" >> $GITHUB_ENV
46+
echo "NEXT_PUBLIC_INFISICAL_UP=true" >> $GITHUB_ENV
47+
echo "CODEBUFF_GITHUB_TOKEN=${{ secrets.CODEBUFF_GITHUB_TOKEN }}" >> $GITHUB_ENV
48+
49+
- name: Build SDK
50+
run: cd sdk && bun run build
51+
52+
- name: Run .agents e2e tests
53+
run: cd .agents && bun run test:e2e --timeout=120000

sdk/e2e/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ bun run test:e2e && bun run test:integration && bun run test:unit:e2e
9595

9696
## Prerequisites
9797

98-
- **API Key**: Set `CODEBUFF_API_KEY` environment variable for E2E and integration tests
98+
- **API Key**: Set `CODEBUFF_API_KEY` for E2E and integration tests
99+
- **Opt-in**: Set `RUN_CODEBUFF_E2E=true` for local live API runs (CI runs automatically)
99100
- Tests skip gracefully if API key is not set
100101

101102
## Writing Tests

sdk/e2e/utils/get-api-key.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@ export function getApiKey(): string {
1919
* Skip test if no API key is available (for CI environments without credentials).
2020
*/
2121
export function skipIfNoApiKey(): boolean {
22-
return !process.env.CODEBUFF_API_KEY
22+
const apiKey = process.env.CODEBUFF_API_KEY
23+
if (!apiKey) return true
24+
25+
const isCi =
26+
process.env.CI === 'true' ||
27+
process.env.CI === '1' ||
28+
process.env.GITHUB_ACTIONS === 'true'
29+
const optedIn = process.env.RUN_CODEBUFF_E2E === 'true'
30+
31+
return !(isCi || optedIn)
2332
}
2433

2534
/**

sdk/src/__tests__/run.integration.test.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,79 @@
1-
import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants'
2-
import { describe, expect, it } from 'bun:test'
1+
import { afterEach, describe, expect, it, mock, spyOn } from 'bun:test'
32

3+
import { assistantMessage, userMessage } from '@codebuff/common/util/messages'
44
import { CodebuffClient } from '../client'
5+
import * as databaseModule from '../impl/database'
6+
import * as mainPromptModule from '@codebuff/agent-runtime/main-prompt'
57

68
describe('Prompt Caching', () => {
9+
afterEach(() => {
10+
mock.restore()
11+
})
12+
713
it(
814
'should be cheaper on second request',
915
async () => {
16+
spyOn(databaseModule, 'getUserInfoFromApiKey').mockResolvedValue({
17+
id: 'user-123',
18+
} as any)
19+
20+
spyOn(mainPromptModule, 'callMainPrompt').mockImplementation(
21+
async (params) => {
22+
const { sendAction, action: promptAction, promptId } = params
23+
const sessionState = promptAction.sessionState
24+
const hasHistory =
25+
sessionState.mainAgentState.messageHistory.length > 0
26+
const creditsUsed = hasHistory ? 10 : 100
27+
28+
sessionState.mainAgentState.creditsUsed = creditsUsed
29+
sessionState.mainAgentState.directCreditsUsed = creditsUsed
30+
31+
if (promptAction.prompt) {
32+
sessionState.mainAgentState.messageHistory.push(
33+
userMessage(promptAction.prompt),
34+
assistantMessage('hi'),
35+
)
36+
}
37+
38+
await sendAction({
39+
action: {
40+
type: 'response-chunk',
41+
userInputId: promptId,
42+
chunk: {
43+
type: 'finish',
44+
totalCost: creditsUsed,
45+
},
46+
},
47+
})
48+
49+
const output = {
50+
type: 'lastMessage' as const,
51+
value: sessionState.mainAgentState.messageHistory.slice(-1),
52+
}
53+
54+
await sendAction({
55+
action: {
56+
type: 'prompt-response',
57+
promptId,
58+
sessionState,
59+
output,
60+
},
61+
})
62+
63+
return {
64+
sessionState,
65+
output,
66+
}
67+
},
68+
)
69+
1070
const filler =
1171
`Run UUID: ${crypto.randomUUID()} ` +
1272
'Ignore this text. This is just to make the prompt longer. '.repeat(500)
1373
const prompt = 'respond with "hi"'
1474

1575
const client = new CodebuffClient({
16-
apiKey: process.env[API_KEY_ENV_VAR]!,
76+
apiKey: 'test-api-key',
1777
})
1878
let cost1 = -1
1979
const run1 = await client.run({

sdk/test/setup-env.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const testDefaults: Record<string, string> = {
1313
'https://billing.stripe.com/p/login/test_placeholder',
1414
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID: 'test-verification',
1515
NEXT_PUBLIC_WEB_PORT: '3000',
16-
CODEBUFF_API_KEY: 'test-api-key',
1716
}
1817

1918
for (const [key, value] of Object.entries(testDefaults)) {

0 commit comments

Comments
 (0)