Packages
A well-typed, idiomatic Gleam client for Anthropic's Claude API with streaming support and tool use
Current section
Files
Jump to
Current section
Files
src/anthropic/testing.gleam
//// Testing utilities for Anthropic API client
////
//// This module provides mock responses, test fixtures, and helpers
//// for testing code that uses the anthropic_gleam library.
import anthropic/types/message
import anthropic/types/request.{type CreateMessageResponse}
import gleam/erlang/charlist
import gleam/http/response.{type Response}
import gleam/json
import gleam/option.{None, Some}
// =============================================================================
// Mock Response Builders
// =============================================================================
/// Create a mock successful text response
pub fn mock_text_response(text: String) -> Response(String) {
response.new(200)
|> response.set_body(mock_text_response_body("msg_mock_123", text))
}
/// Create a mock successful tool use response
pub fn mock_tool_use_response(
tool_id: String,
tool_name: String,
tool_input: String,
) -> Response(String) {
response.new(200)
|> response.set_body(mock_tool_use_response_body(
"msg_mock_456",
tool_id,
tool_name,
tool_input,
))
}
/// Create a mock error response
pub fn mock_error_response(
status_code: Int,
error_type: String,
message: String,
) -> Response(String) {
response.new(status_code)
|> response.set_body(mock_error_body(error_type, message))
}
/// Create a mock authentication error response
pub fn mock_auth_error() -> Response(String) {
mock_error_response(401, "authentication_error", "Invalid API key")
}
/// Create a mock rate limit error response
pub fn mock_rate_limit_error() -> Response(String) {
mock_error_response(429, "rate_limit_error", "Rate limit exceeded")
}
/// Create a mock overloaded error response
pub fn mock_overloaded_error() -> Response(String) {
mock_error_response(529, "overloaded_error", "API is temporarily overloaded")
}
/// Create a mock invalid request error response
pub fn mock_invalid_request_error(message: String) -> Response(String) {
mock_error_response(400, "invalid_request_error", message)
}
// =============================================================================
// Response Body Builders
// =============================================================================
/// Build a mock text response body JSON
pub fn mock_text_response_body(id: String, text: String) -> String {
json.to_string(
json.object([
#("id", json.string(id)),
#("type", json.string("message")),
#("role", json.string("assistant")),
#(
"content",
json.array(
[
json.object([
#("type", json.string("text")),
#("text", json.string(text)),
]),
],
fn(x) { x },
),
),
#("model", json.string("claude-sonnet-4-20250514")),
#("stop_reason", json.string("end_turn")),
#(
"usage",
json.object([
#("input_tokens", json.int(10)),
#("output_tokens", json.int(20)),
]),
),
]),
)
}
/// Build a mock tool use response body JSON
pub fn mock_tool_use_response_body(
id: String,
tool_id: String,
tool_name: String,
_tool_input: String,
) -> String {
// Note: tool_input is currently not used as we generate a mock empty object
// In a real implementation, you might want to parse and include the input
json.to_string(
json.object([
#("id", json.string(id)),
#("type", json.string("message")),
#("role", json.string("assistant")),
#(
"content",
json.array(
[
json.object([
#("type", json.string("tool_use")),
#("id", json.string(tool_id)),
#("name", json.string(tool_name)),
#("input", json.object([])),
]),
],
fn(x) { x },
),
),
#("model", json.string("claude-sonnet-4-20250514")),
#("stop_reason", json.string("tool_use")),
#(
"usage",
json.object([
#("input_tokens", json.int(15)),
#("output_tokens", json.int(25)),
]),
),
]),
)
}
/// Build a mock error body JSON
pub fn mock_error_body(error_type: String, message: String) -> String {
json.to_string(
json.object([
#("type", json.string("error")),
#(
"error",
json.object([
#("type", json.string(error_type)),
#("message", json.string(message)),
]),
),
]),
)
}
// =============================================================================
// Test Fixtures
// =============================================================================
/// A simple text response fixture
pub fn fixture_simple_response() -> CreateMessageResponse {
request.CreateMessageResponse(
id: "msg_fixture_001",
response_type: "message",
role: message.Assistant,
content: [message.TextBlock(text: "Hello! How can I help you today?")],
model: "claude-sonnet-4-20250514",
stop_reason: Some(request.EndTurn),
stop_sequence: None,
usage: request.Usage(input_tokens: 12, output_tokens: 8),
)
}
/// A multi-turn conversation response fixture
pub fn fixture_conversation_response() -> CreateMessageResponse {
request.CreateMessageResponse(
id: "msg_fixture_002",
response_type: "message",
role: message.Assistant,
content: [
message.TextBlock(
text: "Based on our previous conversation, I understand you're asking about Gleam programming.",
),
],
model: "claude-sonnet-4-20250514",
stop_reason: Some(request.EndTurn),
stop_sequence: None,
usage: request.Usage(input_tokens: 150, output_tokens: 45),
)
}
/// A tool use response fixture
pub fn fixture_tool_use_response() -> CreateMessageResponse {
request.CreateMessageResponse(
id: "msg_fixture_003",
response_type: "message",
role: message.Assistant,
content: [
message.TextBlock(text: "Let me check the weather for you."),
message.ToolUseBlock(
id: "toolu_fixture_001",
name: "get_weather",
input: "{\"location\":\"San Francisco\",\"unit\":\"celsius\"}",
),
],
model: "claude-sonnet-4-20250514",
stop_reason: Some(request.ToolUse),
stop_sequence: None,
usage: request.Usage(input_tokens: 25, output_tokens: 35),
)
}
/// A max tokens response fixture
pub fn fixture_max_tokens_response() -> CreateMessageResponse {
request.CreateMessageResponse(
id: "msg_fixture_004",
response_type: "message",
role: message.Assistant,
content: [
message.TextBlock(
text: "This response was truncated because it reached the maximum token limit...",
),
],
model: "claude-sonnet-4-20250514",
stop_reason: Some(request.MaxTokens),
stop_sequence: None,
usage: request.Usage(input_tokens: 20, output_tokens: 100),
)
}
/// A stop sequence response fixture
pub fn fixture_stop_sequence_response() -> CreateMessageResponse {
request.CreateMessageResponse(
id: "msg_fixture_005",
response_type: "message",
role: message.Assistant,
content: [message.TextBlock(text: "The answer is 42")],
model: "claude-sonnet-4-20250514",
stop_reason: Some(request.StopSequence),
stop_sequence: Some("END"),
usage: request.Usage(input_tokens: 15, output_tokens: 5),
)
}
// =============================================================================
// Integration Test Helpers
// =============================================================================
/// Check if an API key is available for integration tests
pub fn has_api_key() -> Bool {
case get_env("ANTHROPIC_API_KEY") {
Ok(key) -> key != ""
Error(_) -> False
}
}
/// Get environment variable using charlist conversion
@external(erlang, "os", "getenv")
fn ffi_getenv(
name: charlist.Charlist,
default: charlist.Charlist,
) -> charlist.Charlist
fn get_env(name: String) -> Result(String, Nil) {
let value =
ffi_getenv(charlist.from_string(name), charlist.from_string(""))
|> charlist.to_string
case value {
"" -> Error(Nil)
v -> Ok(v)
}
}