fabro/lib/foundation/fabro-api/tests/provider_round_trip.rs
Bryan Helmkamp 619cb44e3c
Import lithos-llm types directly instead of through fabro-types
fabro-types no longer re-exports the lithos catalog and request types
(ProviderId, ModelId, ModelHandle, Message, ContentPart, TokenCounts,
Cost, Speed, ReasoningEffort, ReasoningOutput, and the rest). Every
crate that uses them depends on lithos-llm and names them there, and
the fabro-api progenitor replacements point at the lithos paths.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 10:03:08 -06:00

97 lines
3.4 KiB
Rust

use std::any::{TypeId, type_name};
use fabro_api::types::Provider as ApiProvider;
use fabro_types::Provider;
use lithos_llm::catalog::{ProviderId, builtin};
#[test]
fn provider_reuses_canonical_type() {
assert_same_type::<ApiProvider, Provider>();
}
#[test]
fn provider_json_matches_openapi_shape() {
let provider = Provider {
id: builtin::anthropic(),
display_name: "Anthropic".to_string(),
adapter: "anthropic".to_string(),
base_url: "https://api.anthropic.test".to_string(),
api_key_url: Some("https://console.anthropic.com/settings/keys".to_string()),
priority: 100,
aliases: vec!["claude".to_string()],
model_count: 7,
default_model: Some("claude-opus-4.7".to_string()),
configured: true,
expected_secret_name: Some("ANTHROPIC_API_KEY".to_string()),
};
let json = serde_json::to_value(&provider).unwrap();
assert_eq!(json["id"], "anthropic");
assert_eq!(json["display_name"], "Anthropic");
assert_eq!(json["adapter"], "anthropic");
assert_eq!(json["base_url"], "https://api.anthropic.test");
assert_eq!(
json["api_key_url"],
"https://console.anthropic.com/settings/keys"
);
assert_eq!(json["priority"], 100);
assert_eq!(json["aliases"], serde_json::json!(["claude"]));
assert_eq!(json["model_count"], 7);
assert_eq!(json["default_model"], "claude-opus-4.7");
assert_eq!(json["configured"], true);
assert_eq!(json["expected_secret_name"], "ANTHROPIC_API_KEY");
let round_trip: ApiProvider = serde_json::from_value(json).unwrap();
assert_eq!(round_trip, provider);
}
#[test]
fn provider_omits_optional_fields_when_absent() {
// Proves the required/optional split the OpenAPI `Provider` schema
// declares: the four `skip_serializing_if` fields drop out entirely, while
// the required fields always serialize.
let provider = Provider {
id: ProviderId::new("custom"),
display_name: "Custom".to_string(),
adapter: "openai-compatible".to_string(),
base_url: "https://custom.test/v1".to_string(),
api_key_url: None,
priority: 0,
aliases: Vec::new(),
model_count: 0,
default_model: None,
configured: false,
expected_secret_name: None,
};
let json = serde_json::to_value(&provider).unwrap();
let object = json.as_object().unwrap();
assert!(!object.contains_key("api_key_url"));
assert!(!object.contains_key("aliases"));
assert!(!object.contains_key("default_model"));
assert!(!object.contains_key("expected_secret_name"));
for key in [
"id",
"display_name",
"adapter",
"base_url",
"priority",
"model_count",
"configured",
] {
assert!(object.contains_key(key), "{key} should serialize");
}
let round_trip: ApiProvider = serde_json::from_value(json).unwrap();
assert_eq!(round_trip, provider);
}
fn assert_same_type<T: 'static, U: 'static>() {
assert_eq!(
TypeId::of::<T>(),
TypeId::of::<U>(),
"{} should be the same type as {}",
type_name::<T>(),
type_name::<U>()
);
}