claude-agents-sdk 0.1.7

Rust SDK for building agents with Claude Code CLI
Documentation
# Testing Guide

## Quick Start

```bash
# Unit tests (no auth required)
make test

# Integration tests (requires auth)
claude setup-token          # One-time: generate OAuth token
cp .env.example .env        # One-time: create env file, paste token
make integration-test       # Run all integration tests
```

## Unit Tests

Unit tests run locally without authentication:

```bash
cargo test
# or
make test
```

These test message parsing, option building, and SDK internals.

## Integration Tests

Integration tests run against the real Claude API inside Docker containers for isolation.

### Authentication Setup

1. Generate an OAuth token (requires Claude Pro/Max):
   ```bash
   claude setup-token
   ```

2. Create `.env` file:
   ```bash
   cp .env.example .env
   ```

3. Edit `.env` and add your token:
   ```
   CLAUDE_CODE_OAUTH_TOKEN=your-token-here
   ```

### Running Tests

```bash
# All integration tests
make integration-test

# With verbose output
make integration-test-verbose

# Specific test pattern
./scripts/run-integration-tests.sh test_oneshot

# Interactive shell for debugging
make integration-shell
```

### Test Categories

| Category | File | Description |
|----------|------|-------------|
| Core | `test_core.rs` | One-shot queries, streaming, message format |
| Client | `test_client.rs` | Builder API, multi-turn, model selection |
| Context | `test_context.rs` | Session management, context persistence, turn tracking |
| Concurrent | `test_concurrent.rs` | Parallel sessions, resource isolation |
| Callbacks | `test_callbacks.rs` | Tool permission callbacks |
| Hooks | `test_hooks.rs` | Lifecycle hooks (PreToolUse, PostToolUse) |
| Cancellation | `test_cancellation.rs` | Timeouts, disconnects, task abort |
| Resources | `test_resources.rs` | Process cleanup, no leaks |
| Errors | `test_errors.rs` | Error handling, edge cases |
| Tools | `test_tools.rs` | Tool configuration, result parsing |
| System Prompt | `test_system_prompt.rs` | Custom and preset prompts |
| Results | `test_results.rs` | Cost/budget tracking, token usage |
| Edge Cases | `test_edge_cases.rs` | Unicode, special chars, long prompts |

### Ignored Tests

Some tests are marked `#[ignore]` and require special conditions:

- `test_authentication_failure` - Requires invalid credentials
- `stress_test_*` - Long-running stress tests

Run ignored tests using the shell:
```bash
make integration-shell
# Inside container:
cargo test --features integration-tests -- --ignored --test-threads=1
```

## Test Infrastructure

- **Docker**: Tests run in containers for isolation
- **Single-threaded**: `--test-threads=1` prevents race conditions
- **Timeouts**: All tests have timeouts to prevent hangs
- **Helpers**: Common utilities in `tests/integration/helpers.rs`

## CI/CD Integration

Integration tests run automatically in GitHub Actions when the `CLAUDE_CODE_OAUTH_TOKEN` secret is configured.

### Setting Up CI Integration Tests

1. Go to your repository Settings → Secrets and variables → Actions
2. Add a new repository secret named `CLAUDE_CODE_OAUTH_TOKEN`
3. Set the value to your Claude OAuth token (from `claude setup-token`)

When configured, integration tests will run:
- On every push to `main`
- On every pull request to `main`

The job is skipped gracefully if the secret is not configured, allowing forks and external contributors to run unit tests without API access.

## Writing New Tests

1. Add to appropriate file in `tests/integration/`
2. Use `#![cfg(feature = "integration-tests")]` gate
3. Use helpers: `default_options()`, `collect_messages()`, etc.
4. Add timeouts for operations that could hang
5. Handle both success and acceptable failure cases

Example:
```rust
#[tokio::test]
async fn test_my_feature() {
    let options = default_options();
    let result = tokio::time::timeout(
        Duration::from_secs(30),
        collect_messages("test prompt", options),
    ).await;

    match result {
        Ok(Ok(messages)) => {
            assert!(get_result(&messages).is_some());
        }
        Ok(Err(e)) => {
            // Handle acceptable errors
            eprintln!("Error (may be acceptable): {}", e);
        }
        Err(_) => panic!("Test timed out"),
    }
}
```