use super::*;
use crate::wire::secret::tests::a_config_reloads_without_its_credential;
pub(super) fn recorded(section: &str, relative: &str) -> String {
let root = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../rig-cassette/fixtures/cassettes/openai/"
);
let text = std::fs::read_to_string(format!("{root}{relative}"))
.unwrap_or_else(|error| panic!("cassette {relative} is readable: {error}"));
let (when, then) = text
.split_once("\nthen:\n")
.unwrap_or_else(|| panic!("cassette {relative} has a `then:` section"));
let scope = if section == "when" { when } else { then };
let mut lines = scope.lines();
let body = lines
.by_ref()
.find(|line| line.trim_start().starts_with("body:"))
.and_then(|line| line.split_once("body:"))
.map(|(_, rest)| rest.trim())
.unwrap_or_else(|| panic!("cassette {relative} names a {section} body"));
if let Some(quoted) = body.strip_prefix('\'').and_then(|b| b.strip_suffix('\'')) {
return quoted.replace("''", "'");
}
let mut block = String::new();
for line in lines {
if !line.is_empty() && !line.starts_with(" ") {
break;
}
block.push_str(line.get(4..).unwrap_or_default());
block.push('\n');
}
block
}
pub(super) fn recorded_json(section: &str, relative: &str) -> serde_json::Value {
serde_json::from_str(&recorded(section, relative))
.unwrap_or_else(|error| panic!("cassette {relative} {section} body is JSON: {error}"))
}
#[test]
fn a_serialized_configuration_carries_no_key_material() {
a_config_reloads_without_its_credential(
&OpenAIConfig::new("sk-secret"),
"sk-secret",
|openai| &openai.api_key,
);
for wire in [
serde_json::to_string(&OpenAIConfig::new("sk-secret").chat("gpt-5.2")),
serde_json::to_string(&OpenAIConfig::new("sk-secret").responses("gpt-5.2")),
] {
let json = wire.expect("the wire serializes");
assert!(!json.contains("sk-secret"), "the key leaked: {json}");
}
}
#[test]
fn a_gateway_configuration_round_trips_without_its_credential() {
let chatgpt = crate::providers::chatgpt::DIALECT;
let config = OpenAIConfig::with_key(&chatgpt, "tok-secret").with_account_id("acct-1");
assert_eq!(
config.instructions.as_deref(),
chatgpt.quirks.default_instructions
);
assert!(config.identity.is_some());
let json = serde_json::to_string(&config).expect("serializes");
assert!(!json.contains("tok-secret"), "the token leaked: {json}");
let restored: OpenAIConfig = serde_json::from_str(&json).expect("deserializes");
assert_eq!(restored.dialect, chatgpt);
assert_eq!(restored.account_id.as_deref(), Some("acct-1"));
assert_eq!(restored.instructions, config.instructions);
assert_eq!(restored.identity, config.identity);
assert!(restored.api_key.is_empty());
let wire = config.responses("gpt-5.4");
let restored: super::super::responses_api::wire::Responses =
serde_json::from_str(&serde_json::to_string(&wire).expect("serializes"))
.expect("deserializes");
assert_eq!(restored.system_instructions, wire.system_instructions);
assert_eq!(restored.provider.dialect, chatgpt);
}
fn route_of(wire: &OpenAiWire) -> Option<&'static str> {
use crate::wire::Wire as _;
let request = crate::completion::CompletionRequest::new("hi");
wire.encode(request, crate::wire::Mode::Unary)
.expect("the request encodes")
.route
}
#[test]
fn the_default_completion_wire_is_the_dialects_route_and_round_trips() {
use crate::wire::Wire as _;
let openai = OpenAIConfig::new("sk-secret").completion("gpt-5.2");
let groq = OpenAIConfig::with_key(&GROQ, "gsk-secret").completion("llama-3.3-70b-versatile");
assert!(matches!(openai, OpenAiWire::Responses(_)), "{openai:?}");
assert!(matches!(groq, OpenAiWire::Chat(_)), "{groq:?}");
assert_eq!(route_of(&openai), Some("/responses"));
assert_eq!(route_of(&groq), Some("/chat/completions"));
for (wire, secret) in [(openai, "sk-secret"), (groq, "gsk-secret")] {
let json = serde_json::to_string(&wire).expect("the wire serializes");
assert!(!json.contains(secret), "the key leaked: {json}");
let restored: OpenAiWire = serde_json::from_str(&json).expect("the wire deserializes");
assert_eq!(restored.describe().model, wire.describe().model);
assert_eq!(restored.provider().dialect, wire.provider().dialect);
assert_eq!(
std::mem::discriminant(&restored),
std::mem::discriminant(&wire)
);
}
}
#[test]
fn a_configured_route_overrides_the_dialects_and_round_trips() {
let on_chat = OpenAIConfig::new("sk-secret").with_route(Route::Chat);
let on_responses = OpenAIConfig::with_key(&GROQ, "gsk-secret").with_route(Route::Responses);
assert_eq!(on_chat.completion_route(), Route::Chat);
assert_eq!(on_responses.completion_route(), Route::Responses);
assert_eq!(
route_of(&on_chat.completion("gpt-5.2")),
Some("/chat/completions")
);
assert_eq!(
route_of(&on_responses.completion("llama")),
Some("/responses")
);
let restored: OpenAIConfig =
serde_json::from_str(&serde_json::to_string(&on_chat).expect("serializes"))
.expect("deserializes");
assert_eq!(restored.completion_route(), Route::Chat);
assert!(
matches!(restored.completion("gpt-5.2"), OpenAiWire::Chat(_)),
"the configured route survives storage"
);
let json = serde_json::to_string(&OpenAIConfig::new("sk-secret")).expect("serializes");
assert!(!json.contains("route"), "{json}");
}
#[test]
fn a_dialect_round_trips_through_its_name() {
let json = serde_json::to_string(&GROQ).expect("a dialect serializes");
assert_eq!(json, "\"groq\"");
assert_eq!(
serde_json::from_str::<Dialect>(&json).expect("a dialect deserializes"),
GROQ
);
let config = OpenAIConfig::new("k").with_dialect(&MISTRAL);
let restored: OpenAIConfig =
serde_json::from_str(&serde_json::to_string(&config).expect("serializes"))
.expect("deserializes");
assert_eq!(restored.dialect, MISTRAL);
assert_eq!(restored.base_url, MISTRAL.base_url);
}
#[test]
fn an_unknown_dialect_name_is_rejected() {
let error = serde_json::from_str::<Dialect>("\"not-a-provider\"")
.expect_err("an unknown dialect is rejected");
assert!(
error.to_string().contains("not-a-provider"),
"the error names the dialect: {error}"
);
}
#[test]
fn the_dialect_decides_the_credential_header() {
fn headers(provider: &OpenAIConfig) -> http::HeaderMap {
provider
.authenticate(http::Request::get("https://example.invalid/"))
.body(())
.expect("builds")
.headers()
.clone()
}
let openai = headers(&OpenAIConfig::new("sk-test"));
assert_eq!(openai["authorization"], "Bearer sk-test");
let azure = headers(&OpenAIConfig::with_key(&AZURE, "azure-key"));
assert_eq!(azure["api-key"], "azure-key");
assert!(!azure.contains_key("authorization"));
let keyless = headers(&OpenAIConfig::with_key(&LLAMACPP, ""));
assert!(
!keyless.contains_key("authorization"),
"`llama-server` rejects a request carrying a key it was not started with"
);
let keyed = headers(&OpenAIConfig::with_key(&LLAMACPP, "local"));
assert_eq!(keyed["authorization"], "Bearer local");
}
#[test]
fn a_dialect_without_a_verify_endpoint_refuses_to_invent_one() {
use crate::wire::{Mode, Wire};
assert!(
OpenAIConfig::with_key(&PERPLEXITY, "k")
.verify()
.encode((), Mode::Unary)
.is_err()
);
assert!(
OpenAIConfig::new("k")
.verify()
.encode((), Mode::Unary)
.is_ok()
);
}
#[test]
fn azure_accepts_either_credential_under_its_own_header() {
fn headers(provider: &OpenAIConfig) -> http::HeaderMap {
provider
.authenticate(http::Request::get("https://example.invalid/"))
.body(())
.expect("builds")
.headers()
.clone()
}
let alternative = AZURE
.alternate_auth
.expect("azure accepts a second credential");
assert_eq!(alternative.api_key_env, "AZURE_TOKEN");
assert_eq!(alternative.auth, Auth::Bearer);
assert_eq!(AZURE.api_key_env, "AZURE_API_KEY");
let keyed = headers(&OpenAIConfig::with_key(&AZURE, "account-key"));
assert_eq!(keyed["api-key"], "account-key");
assert!(!keyed.contains_key("authorization"));
let token = headers(&OpenAIConfig::with_alternate_key(&AZURE, "entra-token"));
assert_eq!(token["authorization"], "Bearer entra-token");
assert!(
!token.contains_key("api-key"),
"an Entra token is not an account key"
);
}
#[test]
fn the_huggingface_sub_route_decides_the_model_and_the_routes() {
assert_eq!(
SubRoute::Fireworks.model_identifier("llama-3.3-70b"),
"accounts/fireworks/models/llama-3.3-70b"
);
assert_eq!(
SubRoute::Fireworks.model_identifier("accounts/fireworks/models/llama-3.3-70b"),
"accounts/fireworks/models/llama-3.3-70b"
);
assert_eq!(
SubRoute::Together.model_identifier("llama-3.3-70b"),
"llama-3.3-70b"
);
assert_eq!(SubRoute::HFInference.slug(), "hf-inference/models");
assert_eq!(SubRoute::Fireworks.slug(), "fireworks-ai");
assert_eq!(SubRoute::from("my-route").slug(), "my-route");
let default = OpenAIConfig::with_key(&HUGGINGFACE, "hf");
assert_eq!(
default
.modality_uri(
"transcription",
"/audio/transcriptions",
"openai/whisper-large-v3"
)
.expect("hf-inference serves transcription"),
"https://router.huggingface.co/openai/whisper-large-v3"
);
let routed = default.clone().with_sub_route(SubRoute::Together);
let error = routed
.modality_uri("transcription", "/audio/transcriptions", "whisper")
.expect_err("only hf-inference serves transcription");
assert_eq!(
error,
"transcription endpoint is not supported yet for together"
);
assert!(
routed
.modality_uri("image generation", "/images/generations", "sd")
.is_err()
);
assert_eq!(
OpenAIConfig::new("k")
.modality_uri("transcription", "/audio/transcriptions", "whisper-1")
.expect("openai serves transcription"),
"https://api.openai.com/v1/audio/transcriptions"
);
}
#[test]
fn every_dialects_listing_and_verify_urls_match_the_recorded_paths() {
let expected: &[(&Dialect, &str, Option<&str>)] = &[
(
&OPENAI,
"https://api.openai.com/v1/models",
Some("https://api.openai.com/v1/models"),
),
(
&DEEPSEEK,
"https://api.deepseek.com/models",
Some("https://api.deepseek.com/user/balance"),
),
(
&GROQ,
"https://api.groq.com/openai/v1/models",
Some("https://api.groq.com/openai/v1/models"),
),
(
&MISTRAL,
"https://api.mistral.ai/v1/models",
Some("https://api.mistral.ai/v1/models"),
),
(
&OPENROUTER,
"https://openrouter.ai/api/v1/models",
Some("https://openrouter.ai/api/v1/key"),
),
(
&VENICE,
"https://api.venice.ai/api/v1/models",
Some("https://api.venice.ai/api/v1/models"),
),
(
&LLAMACPP,
"http://localhost:8080/v1/models",
Some("http://localhost:8080/props"),
),
(
&MIRA,
"https://api.mira.network/v1/models",
Some("https://api.mira.network/user-credits"),
),
(
&HUGGINGFACE,
"https://router.huggingface.co/models",
Some("https://router.huggingface.co/api/whoami-v2"),
),
(
&TOGETHER,
"https://api.together.xyz/v1/models",
Some("https://api.together.xyz/models"),
),
(&PERPLEXITY, "https://api.perplexity.ai/models", None),
(
&crate::providers::xai::DIALECT,
"https://api.x.ai/v1/models",
Some("https://api.x.ai/v1/api-key"),
),
];
for (dialect, models, verify) in expected {
let provider = OpenAIConfig::with_key(dialect, "k");
assert_eq!(
provider.uri(dialect.quirks.models_path, None),
*models,
"{} model-listing URL",
dialect.name
);
match verify {
Some(verify) => assert_eq!(
provider.uri(dialect.quirks.verify_path, None),
*verify,
"{} verify URL",
dialect.name
),
None => assert!(
dialect.quirks.verify_path.is_empty(),
"{} has no token-free credential check",
dialect.name
),
}
}
}
#[test]
fn llamacpp_serves_its_operational_routes_unversioned() {
let provider = OpenAIConfig::with_key(&LLAMACPP, "");
for route in LLAMACPP.quirks.root_relative_routes {
assert_eq!(
provider.uri(route, None),
format!("http://localhost:8080{route}"),
"{route} is a root route"
);
}
assert_eq!(
provider.uri("/chat/completions", None),
"http://localhost:8080/v1/chat/completions"
);
assert_eq!(
provider.uri("/rerank", None),
"http://localhost:8080/v1/rerank"
);
for dialect in all() {
if dialect.name != "llamacpp" {
assert!(
dialect.quirks.root_relative_routes.is_empty(),
"{} declares root routes it never had",
dialect.name
);
}
}
}