Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
91 changes: 91 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Contributing to ClawShell

## Prerequisites

- Rust (stable toolchain)
- `cargo-insta` for snapshot review: `cargo install cargo-insta`

## Running Tests

Run the full test suite:

```sh
cargo test
```

This executes three test binaries:

| Binary | Source | What it covers |
|---|---|---|
| `integration` | `tests/integration.rs` | End-to-end proxy, DLP scanning, key mapping, AppState |
| `config_fixtures` | `tests/config_fixtures.rs` | Config parsing via fixture files + insta snapshots |
| `cli_tests` | `tests/cli_tests.rs` | CLI argument handling |

### Config Fixture Tests

Config parsing is tested with a data-driven approach using [`datatest-stable`](https://crates.io/crates/datatest-stable) and [`insta`](https://crates.io/crates/insta) snapshots.

**Structure:**

```
tests/
config_fixtures.rs # test harness (no need to edit for new cases)
fixtures/config/
valid/ # configs that must parse successfully
minimal.toml
all_fields.toml
...
invalid/ # configs that must fail to parse
missing_server.toml
port_string.toml
...
snapshots/ # insta snapshots (auto-generated)
config_fixtures__*.snap
```

- **Valid fixtures** are snapshot-tested against their full parsed output, including derived values (`listen_addr`, `upstream_url`, `key_map`).
- **Invalid fixtures** are snapshot-tested against their error messages.

**Adding a new config test case:**

1. Create a `.toml` file in `tests/fixtures/config/valid/` or `tests/fixtures/config/invalid/`.
2. Run the tests — new cases will fail because no snapshot exists yet:
```sh
cargo test --test config_fixtures
```
3. Review and accept the new snapshots:
```sh
cargo insta review
```
4. Commit the `.toml` fixture and the `.snap` snapshot file together.

**Updating snapshots after a config struct change:**

If you modify config structs or default values, existing snapshots will fail. Update them:

```sh
cargo insta test --review
```

This runs all tests, then opens an interactive review for any changed snapshots.

### Integration Tests

Integration tests in `tests/integration.rs` use [`wiremock`](https://crates.io/crates/wiremock) to mock upstream API servers. They cover:

- Proxy request forwarding and header injection
- Virtual-to-real key resolution
- DLP blocking and redaction (request and response)
- Streaming response passthrough
- Error handling (unknown keys, unsupported methods)

Run only integration tests:

```sh
cargo test --test integration
```

## Code Style

- Run `cargo fmt` before committing.
- Run `cargo clippy` and address warnings.
125 changes: 124 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@ console = "0.16.2"
tokio = { version = "1.49", features = ["full", "test-util"] }
tower = { version = "0.5.3", features = ["util"] }
wiremock = "0.6"
datatest-stable = "0.3"
insta = { version = "1", features = ["yaml"] }

[[test]]
name = "config_fixtures"
harness = false
18 changes: 9 additions & 9 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use regex::Regex;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::Path;

#[derive(Debug, Default, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum Provider {
#[default]
Expand All @@ -20,7 +20,7 @@ impl Provider {
}
}

#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Config {
pub server: ServerConfig,
pub upstream: UpstreamConfig,
Expand All @@ -36,7 +36,7 @@ fn default_log_level() -> String {
"info".to_string()
}

#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ServerConfig {
#[serde(default = "default_host")]
pub host: String,
Expand All @@ -52,7 +52,7 @@ fn default_port() -> u16 {
18790
}

#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct UpstreamConfig {
#[serde(default = "default_base_url")]
pub base_url: String,
Expand All @@ -70,23 +70,23 @@ fn default_base_url() -> String {
"https://api.openai.com".to_string()
}

#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct KeyMapping {
pub virtual_key: String,
pub real_key: String,
#[serde(default)]
pub provider: Provider,
}

#[derive(Debug, Default, Deserialize, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum DlpAction {
#[default]
Block,
Redact,
}

#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DlpConfig {
#[serde(default)]
pub patterns: Vec<DlpPattern>,
Expand All @@ -107,7 +107,7 @@ impl Default for DlpConfig {
}
}

#[derive(Debug, Deserialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DlpPattern {
pub name: String,
pub regex: String,
Expand Down
Loading