Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
7630e63
test(tests): add JWT auth and interactive process helpers
May 27, 2026
7502bdd
test(tests): fix edge cases in JWT auth and interactive helpers
May 27, 2026
94f4e24
test(tests): add vitest.agent.config.ts with session-scoped build fix…
May 27, 2026
08bf805
test(tests): fix vitest.agent.config.ts for Vitest 4 poolOptions removal
May 27, 2026
fd9cc15
test(tests): add TC-002 (doctor --verbose) and TC-003 (doctor JWT pro…
May 27, 2026
6afc09d
test(tests): cache spawnSync result in TC-003 doctor test
May 27, 2026
bbd22a5
test(tests): add profile management tests TC-004..TC-010, TC-032, TC-033
May 27, 2026
0099af6
test(tests): move profile mutations to beforeAll for test isolation
May 27, 2026
be7387d
test(tests): add JWT skills lifecycle tests TC-012 and TC-013
May 27, 2026
88cb3f0
test(tests): add models list test TC-022
May 27, 2026
608978e
test(tests): add assistants chat tests TC-014 and TC-015
May 27, 2026
51a3d7d
test(tests): add JWT basic agent tests TC-016..TC-019 and TC-031
May 27, 2026
ac71df6
test(tests): fix models TC-022 to match CI_CODEMIE_MODEL env var
May 27, 2026
f80bb3a
test(tests): fix cleanEnv to use minimal allowlist in agent-jwt-basic
May 27, 2026
c9fa14c
test(tests): fix assistants spec gaps — list cmd, writeJwtProfile, me…
May 27, 2026
c965e4b
test(tests): fix quality issues in agent-jwt-basic — env fallbacks, s…
May 27, 2026
5d1db67
test(tests): add model selection tests TC-020 and TC-021
May 27, 2026
fdcc057
test(tests): add interactive session tests TC-024, TC-025, TC-026
May 27, 2026
153f8fa
test(tests): add diagnostic message to TC-028 status assertion
May 27, 2026
a533ca2
test(tests): guard TC-014 against missing CI_CODEMIE_ASSISTANT_ID
May 27, 2026
c7cddcb
test(tests): guard TC-026 against missing CI_CODEMIE_ASSISTANT_ID
May 27, 2026
b41c181
test(tests): implement CLI integration tests with JWT agent support
Jun 4, 2026
9b4b668
docs(tests): add CLI integration test design spec
Jun 4, 2026
6d9c6d0
chore(ci): revert local-only files from integration test commit
Jun 4, 2026
d81a96b
refactor(utils): use CodeMieClient jwt_token instead of manual servic…
Jun 5, 2026
8e11123
fix(utils): remove stale JWT workaround in chat and guard baseUrl
Jun 5, 2026
b9794d2
test(tests): fix flaky agent integration tests and migrate Vitest 4 p…
Jun 5, 2026
fca9a49
refactor(providers): address MR review comments on JWT plugin
Jun 5, 2026
f42615c
refactor(providers): extract shared agentHooks to default-agent-hooks.ts
Jun 5, 2026
0408b54
refactor(providers): replace hardcoded auth strings with AuthMethod/P…
Jun 5, 2026
c14f2f1
test(tests): remove self-referential TC-027 and add CLI integration t…
Jun 8, 2026
2aece67
chore(deps): bump codemie-sdk from 0.1.428 to 0.1.462
Jun 8, 2026
3906f99
test(tests): move TC-025 skill name to CI_CODEMIE_SKILL_NAME env var
Jun 8, 2026
8d743cf
ci: pass repository vars to test jobs as env variables
Jun 8, 2026
47df47f
test(tests): add agent-task-session hybrid JWT/SSO integration test
Jun 13, 2026
2403f32
ci: retrigger GitHub checks
Jun 15, 2026
8d62687
fix(tests): handle Windows PATH for Claude native installer in global…
Jun 15, 2026
42d8dcd
fix(ci): read credentials from secrets instead of vars
Jun 15, 2026
afc949e
fix(ci): revert AUTH_URL and AUTH_CLIENT_ID back to vars
Jun 15, 2026
9b6349f
fix(tests): update error message in fetchJwtToken
Jun 15, 2026
ee70826
Merge branch 'main' into test/cli-integration-tests
Jun 17, 2026
78fb6cd
Merge branch 'main' into test/cli-integration-tests
Jun 17, 2026
17cc34f
ci(ci): use vars.* for non-secret env vars and improve error messages
Jun 17, 2026
622f241
Merge branch 'main' into test/cli-integration-tests
Jun 17, 2026
5b131f2
ci(ci): pass env vars to agent task session test step
Jun 17, 2026
0246077
ci(ci): add CI_AGENT_MAX_WORKERS to agent test step env
Jun 17, 2026
4344854
ci(ci): use vars for CI_CODEMIE_USERNAME in agent test step
Jun 17, 2026
2a360d7
Merge branch 'main' into test/cli-integration-tests
Jun 17, 2026
5032118
test(tests): remove uncommitted test files from branch
Jun 22, 2026
fbf47b7
test(tests): restore cli-commands tests accidentally removed from branch
Jun 22, 2026
1a5bc00
Merge branch 'refs/heads/main' into test/cli-integration-tests
Jun 22, 2026
4e18ca3
test(assistants): add TC-014/TC-015/TC-026 tests with SSO/JWT isolation
Jun 22, 2026
f4740c9
test(assistants): remove debug file dump from TC-014 PTY test
Jun 22, 2026
a3818d0
fix(tests): restore writeFileSync import removed by mistake
Jun 22, 2026
f677513
test(skills): add TC-025 skill slash-command test with dynamic skill …
Jun 22, 2026
4eace5b
test(tests): rename agent-interactive-session to agent-model-switch
Jun 22, 2026
48d6833
test(tests): add TC-020/TC-021 to agent-model.test.ts, rename from ag…
Jun 22, 2026
345722c
test(tests): convert TC-022 models list test to SSO/JWT dual-mode
Jun 22, 2026
35289e7
test(tests): extract TC-031 health check to cli-commands/health.test.ts
Jun 22, 2026
e90c06f
test(tests): add TC-016 dual-mode task test and TC-027 JWT-only no-pr…
Jun 22, 2026
edb5cef
test(tests): extract TC-018/TC-019 to agent-negative.test.ts
Jun 22, 2026
07b7683
test(tests): move TC-017 to agent-jwt-token, enhance with 2-profile o…
Jun 22, 2026
aab91f3
test(tests): fix TC-017 profile-jwt-override to use bearer-auth type
Jun 22, 2026
bcdc5cb
docs(tests): remove stale specs and update integration test design doc
Jun 22, 2026
b4cd567
test(tests): remove TC-031 health check test and all references
Jun 22, 2026
626aefe
test(tests): move TC-022 models list to agent-model.test.ts
Jun 22, 2026
a6ff2fb
test(config): merge vitest configs into workspace projects
Jun 23, 2026
9df265b
test(tests): add SSO_AVAILABLE guard, remove agent-jwt-budget test
Jun 23, 2026
7f8a4fc
Merge branch 'main' into test/cli-integration-tests
Jun 24, 2026
d8ec84d
Merge branch 'main' into test/cli-integration-tests
Jun 26, 2026
ea76e51
Merge branch 'refs/heads/main' into test/cli-integration-tests
Jun 29, 2026
c41bb5c
test(tests): add TC-029/TC-030, fix PTY input-prompt detection
Jun 29, 2026
63c673a
fix(tests): handle macOS workspace-trust prompt in PTY integration tests
AntonYeromin Jun 30, 2026
e99645c
Merge branch 'main' into test/cli-integration-tests
Jun 30, 2026
3deae94
fix(tests): add run-unique suffixes, stale cleanup, and timeout fixes…
Jun 30, 2026
ef0504f
Merge branch 'main' into test/cli-integration-tests
Jun 30, 2026
b64c126
fix(tests): remove redundant SSO profile setup from agent-model tests
Jul 1, 2026
6b6bc39
Merge branch 'main' into test/cli-integration-tests
Jul 3, 2026
2c12332
Merge branch 'main' into test/cli-integration-tests
Jul 3, 2026
6fec2e7
fix(skills): pass interactive flag to runSkillsCli in add command
AntonYeromin Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 1 addition & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,6 @@ jobs:
run: npm run test:unit

- name: Run integration tests
env:
CI_CODEMIE_USERNAME: ${{ secrets.CI_CODEMIE_USERNAME }}
CI_CODEMIE_PASSWORD: ${{ secrets.CI_CODEMIE_PASSWORD }}
CI_AGENT_MAX_WORKERS: ${{ vars.CI_AGENT_MAX_WORKERS }}
CI_CODEMIE_AUTH_CLIENT_ID: ${{ vars.CI_CODEMIE_AUTH_CLIENT_ID }}
CI_CODEMIE_AUTH_URL: ${{ vars.CI_CODEMIE_AUTH_URL }}
CI_CODEMIE_MODEL: ${{ vars.CI_CODEMIE_MODEL }}
CI_CODEMIE_URL: ${{ vars.CI_CODEMIE_URL }}
CI_IS_LOCAL_RUN: ${{ vars.CI_IS_LOCAL_RUN }}
run: npm run test:integration

test-windows:
Expand Down Expand Up @@ -223,13 +214,4 @@ jobs:
run: npm run test:unit

- name: Run integration tests
env:
CI_CODEMIE_USERNAME: ${{ secrets.CI_CODEMIE_USERNAME }}
CI_CODEMIE_PASSWORD: ${{ secrets.CI_CODEMIE_PASSWORD }}
CI_AGENT_MAX_WORKERS: ${{ vars.CI_AGENT_MAX_WORKERS }}
CI_CODEMIE_AUTH_CLIENT_ID: ${{ vars.CI_CODEMIE_AUTH_CLIENT_ID }}
CI_CODEMIE_AUTH_URL: ${{ vars.CI_CODEMIE_AUTH_URL }}
CI_CODEMIE_MODEL: ${{ vars.CI_CODEMIE_MODEL }}
CI_CODEMIE_URL: ${{ vars.CI_CODEMIE_URL }}
CI_IS_LOCAL_RUN: ${{ vars.CI_IS_LOCAL_RUN }}
run: npm run test:integration
run: npm run test:integration
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ Detailed patterns for architecture, error handling, logging, security, project c
|---|---|
| `require()` and `__dirname` | ES modules and `getDirname(import.meta.url)` |
| Imports without `.js` | Always include `.js` extension |
| Deep relative imports (`../../..`) | `@/` alias (e.g. `@/env/types.js`) |
| Writing tests by default | Tests only on explicit request |
| `child_process.exec` directly | `exec()` from `src/utils/processes.ts` |
| `console.log()` debug output | `logger.debug()` |
Expand Down
297 changes: 297 additions & 0 deletions docs/specs/2026-05-27-cli-integration-tests-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
# CLI Integration Test Design

**Last updated:** 2026-06-22
**Branch:** `test/cli-integration-tests`

---

## Overview

Integration tests for the `codemie-claude` CLI binary. Tests are end-to-end: they spawn the compiled binary as a child process (or PTY), drive it with real environment variables, and assert on exit codes, stdout, and session/metrics artefacts written to `CODEMIE_HOME`.

Tests are split into two Vitest configurations:

| Project | Includes | GlobalSetup |
|---|---|---|
| `unit` | `src/**/*.test.ts` | none |
| `cli` | `tests/integration/**/*.test.ts` (excl. `agent-*`) | none |
| `agent` | `tests/integration/agent-*.test.ts` | `tests/setup/agent-build-setup.ts` (build + SSO auth) |

All three projects are defined in a single `vitest.config.ts` using `defineWorkspace`.

CLI-commands tests (`cli-commands/*.test.ts`) exercise commands that need no network auth (health, help, version, doctor, etc.).
Agent tests (`agent-*.test.ts`) exercise commands that require authentication and make real network calls.

---

## Auth Model

### `CI_IS_LOCAL_RUN` dual-mode

Tests gate on the `CI_IS_LOCAL_RUN` flag (read via `getTestEnvFlagOrDefault('CI_IS_LOCAL_RUN', true)`):

| Value | Mode | Auth mechanism |
|---|---|---|
| `true` (default) | SSO / local dev | Existing `sso-autotest` profile in `~/.codemie` |
| `false` | JWT / CI pipeline | Bearer token fetched from `CI_CODEMIE_AUTH_URL` |

This replaces the old `INCLUDE_JWT_TESTS=true` gate that ran JWT tests only. Tests that can exercise both modes are now **dual-mode** and run in every environment. Tests that are logically specific to the JWT mechanism are marked **JWT-only** and wrapped in `describe.runIf(!CI_IS_LOCAL_RUN)`.

### JWT-only describe convention

```ts
describe.runIf(!CI_IS_LOCAL_RUN)(
'TC-NNN — description [JWT-only, skipped when CI_IS_LOCAL_RUN=true]',
() => { ... },
);
```

The skip reason is embedded in the describe name so it appears in test output when the suite is skipped.

---

## Environment Variables

| Variable | Required | Description |
|---|---|---|
| `CI_IS_LOCAL_RUN` | optional | `true` = SSO mode (default), `false` = JWT mode |
| `CI_CODEMIE_URL` | both modes | CodeMie frontend URL (API base derived as `CI_CODEMIE_URL/code-assistant-api`) |
| `CI_CODEMIE_MODEL` | optional | Model override (default: `claude-sonnet-4-6`) |
| `CI_CODEMIE_USERNAME` | JWT mode | Username for token fetch |
| `CI_CODEMIE_PASSWORD` | JWT mode | Password for token fetch |
| `CI_CODEMIE_AUTH_URL` | JWT mode | Keycloak/auth server base URL |
| `CI_AGENT_MAX_WORKERS` | optional | `maxWorkers` for agent test runner (default: 2) |
| `DEFAULT_TIMEOUT` | optional | Command timeout in seconds (default: 60) |
| `CODEMIE_HOME` | set per-test | Isolated temp dir; overrides `~/.codemie` for the test run |

Local dev: set these in `.env.test.local` at the repo root (gitignored).

---

## Directory Layout

```
tests/
helpers/
index.ts # Re-exports all helpers
cli-runner.ts # CLIRunner, createCLIRunner, createAgentRunner
jwt-auth.ts # fetchJwtToken, writeJwtProfile, jwtCleanEnv
sso-auth.ts # writeSsoProfile, ssoCleanEnv, copySsoCredentials,
# setupSsoAutotestProfile, teardownSsoAutotestProfile
pty-session.ts # spawnPty, PtySession
metrics.ts # getLatestMetricsRecord
temp-workspace.ts # TempWorkspace, createTempWorkspace, getTempDir, resolveLongPath
interactive-helpers.ts # waitForOutput, cleanKill (legacy; prefer spawnPty)
test-env.ts # getTestEnvFlag, getTestEnvFlagOrDefault
session-poll.ts # pollForSession
setup/
agent-build-setup.ts # Vitest globalSetup: npm run build + SSO credential auth
load-test-env.ts # Imports .env.test.local at the top of each test file
integration/
agent-task.test.ts # TC-016 dual-mode
agent-task-session.test.ts # Session/metrics artefact validation
agent-model.test.ts # TC-020, TC-021, TC-022, TC-024
agent-skills.test.ts # TC-025
agent-assistant.test.ts # TC-014, TC-015, TC-026
agent-jwt-token.test.ts # TC-017, TC-027 [JWT-only]
agent-negative.test.ts # TC-018 [JWT-only], TC-019 [dual-mode]
agent-setup.test.ts # TC-029 [SSO-only]
agent-shortcuts.test.ts # Slash command smoke tests
cli-commands/
doctor.test.ts
help.test.ts
version.test.ts
list.test.ts
profile.test.ts
skills.test.ts
workflow.test.ts
self-update.test.ts
error-handling.test.ts
```

---

## Helper Layer

### JWT helpers (`helpers/jwt-auth.ts`)

| Helper | Purpose |
|---|---|
| `fetchJwtToken()` | Fetches bearer token from auth server using `CI_CODEMIE_*` env vars |
| `writeJwtProfile(home, { jwtToken })` | Writes a `bearer-auth` profile config to `<home>/codemie-cli.config.json` |
| `jwtCleanEnv()` | Returns a minimal env object (no `CODEMIE_HOME`, no inherited profile) for JWT runs |

### SSO helpers (`helpers/sso-auth.ts`)

| Helper | Purpose |
|---|---|
| `writeSsoProfile(home)` | Writes an `ai-run-sso` profile config to `<home>/codemie-cli.config.json` |
| `ssoCleanEnv()` | Returns a minimal env object for SSO runs (strips inherited auth env vars) |
| `copySsoCredentials(home)` | Copies SSO credential files from `~/.codemie` into the test's isolated `home` |
| `setupSsoAutotestProfile()` | Sets `sso-autotest` as the active profile in `~/.codemie`; returns the original active profile name |
| `teardownSsoAutotestProfile(original)` | Restores the original active profile after the test |

### PTY helper (`helpers/pty-session.ts`)

| Helper | Purpose |
|---|---|
| `spawnPty(args, env, cwd)` → `PtySession` | Spawns `codemie-claude` in a node-pty PTY; returns `{ send(text), waitFor(pattern), close() }` |

Used by interactive tests (TC-024, TC-025) that need to drive a running session with slash commands.

### Metrics helper (`helpers/metrics.ts`)

| Helper | Purpose |
|---|---|
| `getLatestMetricsRecord(sessionsDir)` | Reads `_metrics.jsonl` in `sessionsDir` and returns the latest record as a parsed object |

### Other helpers

| Helper | File | Purpose |
|---|---|---|
| `getTempDir()` | `temp-workspace.ts` | Returns system temp dir, platform-aware |
| `resolveLongPath(p)` | `temp-workspace.ts` | Resolves Windows long path prefix |
| `getTestEnvFlagOrDefault(name, def)` | `test-env.ts` | Reads an env flag as boolean with fallback |
| `pollForSession(dir, opts)` | `session-poll.ts` | Polls for session file creation with timeout |

---

## TC Map

| TC | File | Mode | Description |
|---|---|---|---|
| TC-014 | `agent-assistant.test.ts` | dual | Setup assistants wizard registers assistant as skill |
| TC-015 | `agent-assistant.test.ts` | dual | Assistants chat with invalid ID returns error |
| TC-016 | `agent-task.test.ts` | dual | `--task` exits 0 and agent response appears in stdout |
| TC-017 | `agent-jwt-token.test.ts` | JWT-only | `--profile` + `--jwt-token` overrides active SSO profile; session records `bearer-auth` |
| TC-018 | `agent-negative.test.ts` | JWT-only | Invalid JWT token exits non-zero with auth error |
| TC-019 | `agent-negative.test.ts` | dual | No profile and no token exits non-zero with config error |
| TC-020 | `agent-model.test.ts` | dual | Session records model configured in profile |
| TC-021 | `agent-model.test.ts` | dual | Metrics records configured model in models array |
| TC-022 | `agent-model.test.ts` | dual | `codemie models list` returns the configured model name |
| TC-024 | `agent-model.test.ts` | dual | In-session `/model` slash command records new model in metrics (PTY) |
| TC-025 | `agent-skills.test.ts` | dual | Skill slash command invocation inside running session (PTY) |
| TC-026 | `agent-assistant.test.ts` | dual | Assistants chat non-interactive (random number round-trip) |
| TC-027 | `agent-jwt-token.test.ts` | JWT-only | `--jwt-token` with no profile (empty CODEMIE_HOME) exits 0 and prints agent response |
| TC-029 | `agent-setup.test.ts` | SSO-only | Setup wizard creates SSO profile; config has correct provider, URL, project, model |
| TC-030 | `cli-commands/self-update.test.ts` | no-auth | `self-update --check` exits 0 and outputs current version from package.json |

---

## Gating Patterns

### Dual-mode test (runs in both SSO and JWT)

```ts
const CI_IS_LOCAL_RUN = getTestEnvFlagOrDefault('CI_IS_LOCAL_RUN', true);

beforeAll(async () => {
if (!CI_IS_LOCAL_RUN) {
jwtToken = await fetchJwtToken();
} else {
originalActiveProfile = setupSsoAutotestProfile();
}
}, 30_000);

afterAll(() => {
if (CI_IS_LOCAL_RUN) teardownSsoAutotestProfile(originalActiveProfile);
});

// Inner beforeAll (per-describe):
beforeAll(() => {
testHome = mkdtempSync(join(getTempDir(), 'codemie-test-'));
if (!CI_IS_LOCAL_RUN) {
writeJwtProfile(testHome, { jwtToken });
} else {
writeSsoProfile(testHome);
copySsoCredentials(testHome);
}
result = spawnSync(
process.execPath,
CI_IS_LOCAL_RUN
? [CLAUDE_BIN, '--task', 'Say READY']
: [CLAUDE_BIN, '--task', 'Say READY', '--jwt-token', jwtToken],
{
env: { ...(CI_IS_LOCAL_RUN ? ssoCleanEnv() : jwtCleanEnv()), CODEMIE_HOME: testHome },
encoding: 'utf-8',
timeout: 120_000,
},
);
}, 180_000);
```

### JWT-only test

```ts
describe.runIf(!CI_IS_LOCAL_RUN)(
'TC-NNN — description [JWT-only, skipped when CI_IS_LOCAL_RUN=true]',
() => {
let jwtToken: string;
beforeAll(async () => { jwtToken = await fetchJwtToken(); }, 30_000);
// ... tests using jwtToken
},
);
```

### Interactive PTY test (dual-mode)

```ts
// TC-024, TC-025 — interactive sessions driven via node-pty
const session = await spawnPty(
[CLAUDE_BIN, ...(CI_IS_LOCAL_RUN ? [] : ['--jwt-token', jwtToken])],
{ ...(CI_IS_LOCAL_RUN ? ssoCleanEnv() : jwtCleanEnv()), CODEMIE_HOME: testHome },
testHome,
);
await session.waitFor(/\$/); // prompt
session.send('/model claude-haiku-4-5\n');
await session.waitFor(/switched|model/i);
await session.close();
```

---

## Global Setup (`tests/setup/agent-build-setup.ts`)

Runs once per agent test session (`vitest.config.ts` agent project → `globalSetup`):

1. Loads `.env.test.local`.
2. Runs `npm run build` to produce `dist/`.
3. Ensures `~/.local/bin` is in `PATH` (needed on some Windows CI runners).
4. Installs or skips the native `claude` binary.
5. If `CI_IS_LOCAL_RUN=true`, authenticates the `sso-autotest` profile so SSO credentials are valid for the test session.

---

## Multi-profile Override Pattern (TC-017)

TC-017 exercises the `--profile` + `--jwt-token` runtime override. The test writes a two-profile config where the **active** profile is SSO and the **non-active** profile is `bearer-auth`. Running with `--profile profile-jwt-override --jwt-token <token>` must:

1. Select the non-active profile (not the active SSO one).
2. Use the supplied token for auth.

The observable proof is the `provider` field in the session file. Because `--jwt-token` does **not** mutate the config's `provider` key, the session will record `bearer-auth` only if the non-active JWT profile was actually selected. If the active SSO profile were used instead, `provider` would be `ai-run-sso`.

Config shape written by `writeTwoProfileConfig(testHome)`:

```json
{
"version": 2,
"activeProfile": "profile-sso-active",
"profiles": {
"profile-sso-active": { "provider": "ai-run-sso", "authMethod": "sso" },
"profile-jwt-override": { "provider": "bearer-auth", "authMethod": "jwt" }
}
}
```

Assertion: `session.provider` matches `/bearer-auth/i`.

---

## Conventions

- Every test writes to an isolated `mkdtempSync(...)` temp dir, set as `CODEMIE_HOME`.
- `afterAll` always `rmSync(testHome, { recursive: true, force: true })`.
- `spawnSync` is used for non-interactive `--task` invocations; `spawnPty` for interactive sessions.
- `testTimeout: 180_000` and `hookTimeout: 300_000` in the `agent` project of `vitest.config.ts`.
- TC numbers appear in the `describe` name so they are visible in test output.
Loading
Loading