use tinyagents::registry::{ModelCatalog, ModelCatalogSnapshot};
const CUSTOM_SNAPSHOT: &str = r#"
{
"schema_version": 1,
"snapshot_id": "test-snapshot-1",
"created_at": "2026-01-01T00:00:00Z",
"currency": "USD",
"unit": "token",
"description": "deterministic offline test catalog",
"sources": [
{ "name": "hand-written", "url": "https://example.test/catalog", "retrieved_at": "2026-01-01T00:00:00Z" }
],
"models": [
{
"provider": "acme",
"model_id": "acme-small",
"aliases": ["small", "acme/acme-small"],
"mode": "chat",
"max_input_tokens": 8000,
"max_output_tokens": 2000,
"pricing": { "input_per_token": 0.000001, "output_per_token": 0.000002 },
"capabilities": { "streaming": true, "tool_calling": true, "system_messages": true },
"source": "hand-written"
},
{
"provider": "acme",
"model_id": "acme-large",
"aliases": ["large"],
"mode": "chat",
"max_input_tokens": 200000,
"max_output_tokens": 16000,
"pricing": { "input_per_token": 0.00001, "output_per_token": 0.00003 },
"capabilities": { "tool_calling": true, "vision": true, "json_schema": true },
"source": "hand-written"
}
]
}
"#;
fn custom_catalog() -> ModelCatalog {
ModelCatalog::from_json(CUSTOM_SNAPSHOT).expect("custom snapshot parses")
}
#[test]
fn embedded_seed_catalog_loads_with_provenance_and_entries() {
let catalog = ModelCatalog::seed().expect("embedded seed snapshot loads");
let snapshot = catalog.snapshot();
assert!(snapshot.schema_version >= 1);
assert_eq!(snapshot.currency, "USD");
assert_eq!(snapshot.unit, "token");
assert!(!catalog.models().is_empty(), "seed carries model entries");
}
#[test]
fn custom_snapshot_exposes_metadata_sources_and_entries() {
let catalog = custom_catalog();
let snapshot = catalog.snapshot();
assert_eq!(snapshot.schema_version, 1);
assert_eq!(snapshot.snapshot_id, "test-snapshot-1");
assert_eq!(snapshot.currency, "USD");
assert_eq!(snapshot.unit, "token");
assert_eq!(snapshot.sources.len(), 1);
assert_eq!(snapshot.sources[0].name, "hand-written");
assert_eq!(catalog.models().len(), 2);
}
#[test]
fn looks_up_models_by_provider_id_and_by_alias() {
let catalog = custom_catalog();
let by_id = catalog.get("acme", "acme-small").expect("canonical lookup");
assert_eq!(by_id.model_id, "acme-small");
let by_alias = catalog.get("acme", "small").expect("alias lookup");
assert_eq!(by_alias.model_id, "acme-small");
assert_eq!(
catalog.get_by_model_id("large").expect("alias id").model_id,
"acme-large"
);
assert_eq!(
catalog
.get_by_model_id("acme/acme-small")
.expect("namespaced alias")
.model_id,
"acme-small"
);
}
#[test]
fn unknown_lookups_return_none_rather_than_error() {
let catalog = custom_catalog();
assert!(catalog.get("acme", "ghost-model").is_none());
assert!(catalog.get("other", "acme-small").is_none());
assert!(catalog.get_by_model_id("nonexistent").is_none());
}
#[test]
fn pricing_supports_deterministic_rollup_cost_estimation() {
let catalog = custom_catalog();
let large = catalog.get("acme", "acme-large").expect("large entry");
let small = catalog.get("acme", "acme-small").expect("small entry");
let cost = |entry: &tinyagents::registry::ModelCatalogEntry, input: f64, output: f64| -> f64 {
input * entry.pricing.input_per_token.unwrap_or(0.0)
+ output * entry.pricing.output_per_token.unwrap_or(0.0)
};
let parent = cost(large, 1000.0, 500.0);
assert!((parent - (1000.0 * 0.00001 + 500.0 * 0.00003)).abs() < 1e-12);
let child = cost(small, 2000.0, 1000.0);
assert!((child - (2000.0 * 0.000001 + 1000.0 * 0.000002)).abs() < 1e-12);
let rollup = parent + child;
assert!((rollup - (parent + child)).abs() < 1e-12);
assert!(
child < parent,
"the cheaper sub-model costs less for more work"
);
}
#[test]
fn capability_flags_gate_feature_dispatch() {
let catalog = custom_catalog();
let small = catalog.get("acme", "acme-small").expect("small entry");
let large = catalog.get("acme", "acme-large").expect("large entry");
assert!(small.capabilities.tool_calling);
assert!(large.capabilities.tool_calling);
assert!(!small.capabilities.vision);
assert!(large.capabilities.vision);
assert!(!small.capabilities.json_schema);
assert!(large.capabilities.json_schema);
assert_eq!(small.max_input_tokens, Some(8000));
assert_eq!(large.max_input_tokens, Some(200000));
}
#[test]
fn bridges_catalog_entry_into_a_runtime_profile() {
let catalog = custom_catalog();
let profile = catalog.profile("acme", "acme-large").expect("profile");
assert_eq!(profile.provider.as_deref(), Some("acme"));
assert_eq!(profile.model.as_deref(), Some("acme-large"));
assert!(profile.tool_calling);
assert_eq!(profile.max_input_tokens, Some(200000));
assert!(catalog.profile("acme", "ghost").is_none());
}
#[test]
fn snapshot_round_trips_through_serde() {
let catalog = custom_catalog();
let json = serde_json::to_string(catalog.snapshot()).expect("serialize");
let back: ModelCatalogSnapshot = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.models.len(), 2);
assert_eq!(back.snapshot_id, "test-snapshot-1");
let rewrapped = ModelCatalog::from_snapshot(back);
assert_eq!(
rewrapped.get("acme", "small").expect("alias").model_id,
"acme-small"
);
}