use std::sync::Arc;
use tinyagents::harness::providers::MockModel;
use tinyagents::harness::testkit::FakeTool;
use tinyagents::registry::{AliasBinding, CapabilityRegistry, ComponentKind, DiagnosticSeverity};
#[test]
fn name_only_descriptor_kinds_register_alias_and_resolve() {
let mut reg = CapabilityRegistry::<()>::new();
for (kind, name) in [
(ComponentKind::Store, "kv"),
(ComponentKind::Script, "triage.ragsh"),
(ComponentKind::Middleware, "redact"),
(ComponentKind::Checkpointer, "sqlite"),
(ComponentKind::TaskStore, "jobs"),
(ComponentKind::Listener, "audit"),
] {
reg.register_descriptor(kind, name)
.unwrap_or_else(|e| panic!("register {kind} `{name}` failed: {e}"));
assert!(reg.has(kind, name), "{kind} `{name}` should be present");
}
reg.alias(ComponentKind::Store, "cache", "kv").unwrap();
reg.alias(ComponentKind::TaskStore, "queue", "jobs")
.unwrap();
assert!(reg.has(ComponentKind::Store, "cache"));
assert_eq!(
reg.resolve_name(ComponentKind::Store, "cache").as_deref(),
Some("kv")
);
assert!(!reg.has(ComponentKind::TaskStore, "cache"));
assert_eq!(
reg.names_including_aliases(ComponentKind::Store),
vec!["cache", "kv"]
);
assert_eq!(reg.names(ComponentKind::Store), vec!["kv"]);
}
#[test]
fn registering_over_an_existing_alias_name_is_diagnosed_as_shadowing() {
let mut reg = CapabilityRegistry::<()>::new();
reg.register_model("gpt", Arc::new(MockModel::constant("hi")))
.unwrap();
reg.alias(ComponentKind::Model, "fast", "gpt").unwrap();
assert!(reg.diagnostics().is_empty());
reg.register_model("fast", Arc::new(MockModel::constant("distinct")))
.expect("registering over an alias name is permitted");
assert!(reg.has(ComponentKind::Model, "fast"));
let diagnostics = reg.diagnostics();
let shadow = diagnostics
.iter()
.find(|d| d.name == "fast" && d.severity == DiagnosticSeverity::Warning)
.expect("a Warning should flag the shadowed alias");
assert_eq!(shadow.kind, ComponentKind::Model);
assert!(
shadow.message.contains("shadows") && shadow.message.contains("unreachable"),
"message should explain the alias is shadowed and unreachable: {}",
shadow.message
);
}
#[test]
fn snapshot_projects_alias_bindings_sorted_by_kind_and_alias() {
let mut reg = CapabilityRegistry::<()>::new();
reg.register_model("gpt", Arc::new(MockModel::constant("hi")))
.unwrap();
reg.register_tool(Arc::new(FakeTool::returning("lookup", "ok")))
.unwrap();
reg.alias(ComponentKind::Model, "zeta", "gpt").unwrap();
reg.alias(ComponentKind::Tool, "search", "lookup").unwrap();
reg.alias(ComponentKind::Model, "default", "gpt").unwrap();
let snapshot = reg.snapshot();
assert_eq!(
snapshot.aliases,
vec![
AliasBinding {
kind: ComponentKind::Model,
alias: "default".to_string(),
canonical: "gpt".to_string(),
},
AliasBinding {
kind: ComponentKind::Model,
alias: "zeta".to_string(),
canonical: "gpt".to_string(),
},
AliasBinding {
kind: ComponentKind::Tool,
alias: "search".to_string(),
canonical: "lookup".to_string(),
},
]
);
let gpt = snapshot
.by_kind(ComponentKind::Model)
.into_iter()
.find(|m| m.id.0 == "gpt")
.expect("gpt present");
assert!(gpt.aliases.contains(&"default".to_string()));
assert!(gpt.aliases.contains(&"zeta".to_string()));
}
#[test]
fn aliasing_an_unregistered_target_is_rejected() {
let mut reg = CapabilityRegistry::<()>::new();
reg.register_model("gpt", Arc::new(MockModel::constant("hi")))
.unwrap();
let err = reg
.alias(ComponentKind::Model, "ghost", "missing")
.expect_err("aliasing an unregistered target must fail");
assert!(
err.to_string().contains("target is not registered"),
"unexpected error: {err}"
);
reg.alias(ComponentKind::Model, "fast", "gpt").unwrap();
let dup = reg
.alias(ComponentKind::Model, "fast", "gpt")
.expect_err("re-declaring an alias must fail");
assert!(
dup.to_string().contains("already defined"),
"unexpected error: {dup}"
);
}