๐งช Testing
Tresor uses a two-tier testing strategy: unit tests for individual packages and an integration test for end-to-end verification.
โถ๏ธ Running Testsโ
# Build check โ ensure everything compiles
go build ./...
# Run all unit tests
go test ./...
# Clear test cache (important after making changes)
go clean -testcache
go test ./...
# Run a specific package's tests
go test ./internal/engine/...
# Run E2E integration test (requires a running daemon)
go test -tags=integration ./e2e/...
โ Test Verification Sequenceโ
When making changes, follow this sequence:
- Build:
go build ./...โ ensure everything compiles - Unit tests:
go test ./...โ all package tests must pass - E2E smoke test: Start the daemon with a test config (
./tresor.exe run --config <path>), then rungo test -tags=integration ./e2e/... - Browser verification: Use
agent-browserto verify web UI changes visually
Skipping any step is not acceptable for changes affecting API behavior, gateway logic, or the web UI. Unit tests alone cannot catch rendering bugs, JavaScript errors, or integration issues between layers.
๐ Unit Test Coverageโ
โ๏ธ Configuration (internal/config/)โ
| File | Coverage |
|---|---|
config_test.go | YAML parsing, default values, auto-detect path resolution, nil slice safety, legacy api_format โ api_formats migration |
๐พ Data Layer (internal/store/)โ
| File | Coverage |
|---|---|
store_test.go | Database open/close, schema creation, seed defaults (3 providers: openai-gpt4o, anthropic-sonnet, anthropic-haiku), active_downstream โ match_downstreams migration |
upsert_test.go | Upsert insert/update, empty config no-op, stale cleanup, merge with existing data, cascade delete for downstreams (removes from rule match_downstreams), model add/remove, rule update/delete, alias group delete, api_formats migration |
write_config_test.go | YAML round-trip (DB โ file โ DB), overwrite old data, atomic write (tmp file + rename), format/downstream array fields survive round-trip |
alias_test.go | Alias CRUD operations, activation, auto-deactivate sibling on new activation, find active alias by model, list groups with downstream names, invalid downstream rejection, delete with auto-promotion of siblings, last-in-group delete cleanup, inactive deletion (no promotion) |
rule.go (matching) | MatchRule method (format/downstream filter checks), FindMatchingRules (multi-match collection, format filtering, downstream ID filtering, downstream format filtering, combo filters) |
โก Engine (internal/engine/)โ
| File | Coverage |
|---|---|
engine_test.go | Unknown model returns 404, wildcard rule matching, path-only matching, model priority (exact > path), pipeline execution with body/header transforms, auth header conflict handling, alias overrides downstream resolution, alias rewrites model name in body, alias forwarding without rules, direct model forwarding via output_model_ids, empty model returns 400, format-filtered rule correctly skipped, multi-rule pipeline concatenation, proxy auth validation, missing proxy key returns 401, model list endpoint aggregation |
engine_helper_test.go | Model rewrite in JSON body (parses and updates), non-JSON passthrough (binary content) |
pipeline_test.go | Empty pipeline config parsing, invalid JSON error handling, model name extraction from request bodies, HTTP request copying for safe modification |
๐ API (internal/api/)โ
| File | Coverage |
|---|---|
rules_test.go | Full CRUD (create/read/update/delete), validation (missing name/path rejected), downstream existence validation for match_downstreams, enabled-only toggle support, empty update rejection, format array fields in create/update |
downstreams_test.go | Full CRUD, API key masking in list responses (***), model add/remove operations, empty model ID rejection, not-found handling (404), api_formats handling |
aliases_test.go | Full CRUD with grouped view, missing field rejection, invalid downstream rejection, hot-switch activation, auto-deactivate sibling on activation, method not allowed (405), group delete with count response, not-found group handling (404) |
๐ Middleware (internal/middleware/)โ
| File | Coverage |
|---|---|
auth_test.go | Passthrough when no password set, valid token allows access, wrong token returns 401, missing header returns 401, malformed token format rejected, runtime password update |
๐ Plugins (internal/plugins/)โ
| File | Coverage |
|---|---|
plugins_test.go | Custom header injection verification, OpenAI-to-Anthropic request/response transform accuracy, Anthropic-to-OpenAI request/response transform accuracy, streaming transforms for both directions, registry listing (4 plugins) |
anthropic_image_fix_test.go | No-change passthrough for messages without tool results, single image extraction from tool_result, mixed content handling (text + tool_result), string-valued tool_result preservation, multiple message handling, response no-op, empty base64 data skip |
streaming_test.go | SSE data writing and chunk formatting, done marker handling, OpenAI SSE parsing with early stop detection, Anthropic SSE parsing with early stop detection, Anthropic SSE event writing |
๐ Proxy (internal/proxy/)โ
| File | Coverage |
|---|---|
proxy_test.go | Mode none returns nil (no proxy), env mode reads HTTPS_PROXY variable, auto mode fallback chain (registry โ env โ direct), windows mode registry reading with env fallback, environment variable cleanup between tests |
๐งฌ Integration Test (e2e/)โ
The E2E test (//go:build integration tag) starts a live daemon with a test configuration and validates:
- Health check endpoint responds
- Auth status/login endpoints
- Rule CRUD operations (create, list, read, update, delete)
- Default downstreams are seeded (3 providers)
- Plugin registry contains all built-in plugins (4)
- Alias group operations (create, activate, list)
- Model add/remove on downstreams
- Proxy routing (requests forwarded correctly)
- Web UI accessibility
- Model list endpoint aggregation
- Runtime config endpoint
๐งฉ Test Patternsโ
๐ Table-Driven Testsโ
Most unit tests use Go's table-driven pattern for comprehensive coverage:
func TestSomething(t *testing.T) {
tests := []struct {
name string
input someType
want expectedType
wantErr bool
}{
{"case 1", input1, expected1, false},
{"case 2", input2, expected2, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// test logic
})
}
}
๐พ In-Memory Test Fixturesโ
Tests use temporary directories and in-memory SQLite databases to avoid polluting the real filesystem. Helper functions set up clean test environments with pre-seeded data.