# entelix integration tests — operator pattern
The shipped test suite exercises every codec, transport, and agent
surface against deterministic mocks. **No live-provider tests run
in CI** — they require API keys and are flaky against vendor rate
limits. Operators with credentials run their own integration tests
following the pattern below.
## Recommended layout
```
your-app/
├─ Cargo.toml
└─ tests/
└─ live_anthropic.rs ← marked #[ignore], opt-in only
```
```rust
//! Live Anthropic round-trip — `cargo test -- --ignored`.
use entelix::{ChatModel, ExecutionContext};
use entelix::codecs::AnthropicMessagesCodec;
use entelix::transports::DirectTransport;
use entelix::ir::{Message, ModelRequest};
use entelix::auth::ApiKeyProvider;
use secrecy::SecretString;
#[tokio::test]
#[ignore = "live API call — opt-in via `cargo test -- --ignored`"]
async fn anthropic_round_trip() {
let api_key = std::env::var("ANTHROPIC_API_KEY")
.expect("ANTHROPIC_API_KEY required for live test");
let auth = ApiKeyProvider::anthropic(SecretString::from(api_key));
let transport = DirectTransport::new("https://api.anthropic.com", auth);
let model = ChatModel::new(
AnthropicMessagesCodec::new(),
transport,
"claude-haiku-4-5",
);
let request = ModelRequest::default()
.with_model("claude-haiku-4-5")
.with_messages(vec![Message::user("Reply with exactly: pong")])
.with_max_tokens(20);
let resp = model
.complete(request, &ExecutionContext::new().with_tenant_id("test-tenant"))
.await
.unwrap();
assert!(
resp.content.iter().any(|p| matches!(p, entelix::ir::ContentPart::Text { text, .. } if text.contains("pong"))),
"expected 'pong' in response, got {:?}",
resp.content,
);
}
```
## Running
```bash
ANTHROPIC_API_KEY="sk-ant-..." cargo test --test live_anthropic -- --ignored
```
Without `--ignored`, the test is filtered out by the harness — the
`#[ignore]` attribute is the standard Rust idiom for opt-in tests.
## Per-provider env-var conventions
| Anthropic | `ANTHROPIC_API_KEY` | `AnthropicMessagesCodec` (entelix-core) |
| OpenAI Chat | `OPENAI_API_KEY` | `OpenAiChatCodec` (entelix-core) |
| OpenAI Responses | `OPENAI_API_KEY` | `OpenAiResponsesCodec` (entelix-core) |
| Gemini | `GEMINI_API_KEY` | `GeminiCodec` (entelix-core) |
| Bedrock | `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` + `AWS_REGION` | `BedrockConverseCodec` + `BedrockTransport` (entelix-cloud) |
| Vertex | `GOOGLE_APPLICATION_CREDENTIALS` (path to service-account JSON) | `VertexTransport` (entelix-cloud) |
| Azure Foundry | `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_ENDPOINT` | `FoundryTransport` (entelix-cloud) |
## Cancellation budgets
Live tests should set a deadline so a stuck vendor call doesn't
hang the harness:
```rust
let ctx = ExecutionContext::new()
.with_tenant_id("test-tenant")
.with_deadline(tokio::time::Instant::now() + std::time::Duration::from_secs(30));
```
## Cost discipline
Live tests cost money. Bound them:
- Use the cheapest tier of each provider (`claude-haiku-4-5`, `gpt-5.1-mini`, `gemini-2.0-flash-lite`, `nova-micro`).
- Set `max_tokens` low (50–200).
- Run only on demand, not in CI loops.
Dashboards built from `gen_ai.usage.cost` surface the spend per
test if you forward traces during the run.