#![allow(
clippy::expect_used,
clippy::unwrap_used,
clippy::panic,
clippy::uninlined_format_args,
clippy::redundant_clone,
clippy::needless_raw_string_hashes,
clippy::ignored_unit_patterns,
clippy::useless_vec
)]
use std::sync::Arc;
use async_trait::async_trait;
use base64::Engine as _;
use meerkat_client::TestClient;
use meerkat_core::service::{CreateSessionRequest, SessionError};
use meerkat_mob::{
MobDefinition, MobState, MobStorage, ProfileName, SpawnMemberSpec, ids::MeerkatId,
};
use meerkat_mobkit::{
DiscoverySpec, MobBootstrapOptions, MobBootstrapSpec, MobKitConfig, SessionHook, UnifiedRuntime,
};
const MINIMAL_MOB_TOML: &str = r#"
[mob]
id = "builder-test-mob"
[profiles.worker]
model = "test-model"
"#;
fn test_definition() -> MobDefinition {
MobDefinition::from_toml(MINIMAL_MOB_TOML).expect("parse test mob definition")
}
#[tokio::test]
#[ignore]
async fn test_builder_ephemeral() {
let runtime = UnifiedRuntime::builder()
.definition(test_definition())
.default_llm_client(Arc::new(TestClient::default()))
.build()
.await
.expect("ephemeral build");
assert_eq!(
runtime.mob_handle().status().await.unwrap(),
MobState::Running
);
runtime.mob_handle().stop().await.expect("stop");
}
#[tokio::test]
#[ignore]
async fn test_builder_persistent_default() {
let tmp = tempfile::tempdir().expect("temp dir");
let state_path = tmp.path().join("state");
let runtime = UnifiedRuntime::builder()
.definition(test_definition())
.persistent_state(&state_path)
.default_llm_client(Arc::new(TestClient::default()))
.build()
.await
.expect("persistent build");
assert_eq!(
runtime.mob_handle().status().await.unwrap(),
MobState::Running
);
assert!(
state_path.join("sessions.db").exists(),
"SQLite session store must be created"
);
assert!(
state_path.join("mobkit_console.sqlite").exists(),
"durable MobKit console log must be created"
);
runtime.mob_handle().stop().await.expect("stop");
}
#[tokio::test]
#[ignore]
async fn test_builder_toml_definition() {
let tmp = tempfile::tempdir().expect("temp dir");
let toml_path = tmp.path().join("mob.toml");
std::fs::write(&toml_path, MINIMAL_MOB_TOML).expect("write toml");
let runtime = UnifiedRuntime::builder()
.definition_path(&toml_path)
.default_llm_client(Arc::new(TestClient::default()))
.build()
.await
.expect("toml definition build");
assert_eq!(
runtime.mob_handle().status().await.unwrap(),
MobState::Running
);
runtime.mob_handle().stop().await.expect("stop");
}
#[tokio::test]
#[ignore]
async fn test_builder_capability_flags() {
let runtime = UnifiedRuntime::builder()
.definition(test_definition())
.default_llm_client(Arc::new(TestClient::default()))
.shell(false)
.build()
.await
.expect("build with shell disabled");
assert_eq!(
runtime.mob_handle().status().await.unwrap(),
MobState::Running
);
runtime.mob_handle().stop().await.expect("stop");
}
#[tokio::test]
#[ignore]
async fn test_builder_session_hook_before_create() {
struct LabelInjector;
#[async_trait]
impl SessionHook for LabelInjector {
async fn before_create(&self, req: &mut CreateSessionRequest) -> Result<(), SessionError> {
let labels = req.labels.get_or_insert_with(Default::default);
labels.insert("injected".to_string(), "true".to_string());
Ok(())
}
}
let runtime = UnifiedRuntime::builder()
.definition(test_definition())
.default_llm_client(Arc::new(TestClient::default()))
.session_hook(Arc::new(LabelInjector))
.build()
.await
.expect("build with session hook");
assert_eq!(
runtime.mob_handle().status().await.unwrap(),
MobState::Running
);
runtime.mob_handle().stop().await.expect("stop");
}
#[tokio::test]
#[ignore]
async fn test_builder_mob_spec_escape_hatch() {
let tmp = tempfile::tempdir().expect("temp dir");
let session_path = tmp.path().join("sessions");
std::fs::create_dir_all(&session_path).expect("session path");
let factory = meerkat::AgentFactory::new(&session_path).comms(true);
let session_service = Arc::new(meerkat::build_ephemeral_service(
factory,
meerkat::Config::default(),
16,
));
let spec = MobBootstrapSpec::new(test_definition(), MobStorage::in_memory(), session_service)
.with_options(MobBootstrapOptions {
allow_ephemeral_sessions: true,
notify_orchestrator_on_resume: true,
default_llm_client: Some(Arc::new(TestClient::default())),
});
let runtime = UnifiedRuntime::builder()
.mob_spec(spec)
.module_config(MobKitConfig {
modules: Vec::new(),
discovery: DiscoverySpec {
namespace: String::new(),
modules: Vec::new(),
},
pre_spawn: Vec::new(),
})
.timeout(std::time::Duration::from_secs(30))
.build()
.await
.expect("escape hatch build");
assert_eq!(
runtime.mob_handle().status().await.unwrap(),
MobState::Running
);
runtime.mob_handle().stop().await.expect("stop");
}
#[tokio::test]
#[ignore]
async fn test_builder_defaults() {
let runtime = UnifiedRuntime::builder()
.definition(test_definition())
.default_llm_client(Arc::new(TestClient::default()))
.build()
.await
.expect("build with defaults");
assert_eq!(
runtime.mob_handle().status().await.unwrap(),
MobState::Running
);
runtime.mob_handle().stop().await.expect("stop");
}
#[tokio::test]
#[ignore]
async fn test_builder_persistent_custom_store() {
let tmp = tempfile::tempdir().expect("temp dir");
let state_path = tmp.path().join("state");
std::fs::create_dir_all(&state_path).expect("create state dir");
let custom_db_path = state_path.join("custom_sessions.db");
let custom_store: std::sync::Arc<dyn meerkat::SessionStore> = std::sync::Arc::new(
meerkat_store::SqliteSessionStore::open(&custom_db_path).expect("open custom store"),
);
let runtime = UnifiedRuntime::builder()
.definition(test_definition())
.persistent_state(&state_path)
.session_store(custom_store)
.default_llm_client(Arc::new(TestClient::default()))
.build()
.await
.expect("persistent build with custom store");
assert_eq!(
runtime.mob_handle().status().await.unwrap(),
MobState::Running
);
assert!(
custom_db_path.exists(),
"custom session store db must exist"
);
assert!(
!state_path.join("sessions.db").exists(),
"builder must use custom store, not create default SQLite"
);
runtime.mob_handle().stop().await.expect("stop");
}
#[tokio::test]
async fn test_builder_ephemeral_custom_store_persists_sessions() {
let custom_store: Arc<dyn meerkat::SessionStore> = Arc::new(meerkat::MemoryStore::new());
let definition = MobDefinition::from_toml(
r#"
[mob]
id = "builder-test-mob"
[profiles.worker]
model = "test-model"
[profiles.worker.tools]
comms = true
"#,
)
.expect("parse test mob definition");
let runtime = UnifiedRuntime::builder()
.definition(definition)
.session_store(custom_store.clone())
.default_llm_client(Arc::new(TestClient::default()))
.build()
.await
.expect("ephemeral build with custom store");
let mid = MeerkatId::from("worker:one");
runtime
.mob_handle()
.spawn_spec(SpawnMemberSpec::new(
ProfileName::from("worker"),
mid.clone(),
))
.await
.expect("spawn worker");
let session_id = runtime
.mob_handle()
.resolve_bridge_session_id(&mid)
.await
.expect("spawned worker has a bridge session id");
assert!(
custom_store
.load(&session_id)
.await
.expect("custom store load")
.is_some(),
"ephemeral builder session_store() must wire the custom store into the real session service"
);
runtime.mob_handle().stop().await.expect("stop");
}
#[tokio::test]
async fn test_builder_custom_blob_store_serves_binary_blobs() {
let blob_store: Arc<dyn meerkat_core::BlobStore> =
Arc::new(meerkat_store::MemoryBlobStore::new());
let runtime = UnifiedRuntime::builder()
.definition(test_definition())
.blob_store(blob_store.clone())
.default_llm_client(Arc::new(TestClient::default()))
.build()
.await
.expect("ephemeral build with custom blob store");
let binary_store = runtime
.binary_blob_store()
.expect("builder-created runtime must expose binary blob serving store");
let blob_ref = binary_store
.put_bytes("image/png", bytes::Bytes::from_static(b"tiny-png"))
.await
.expect("binary put");
let served = binary_store
.get_bytes(&blob_ref.blob_id)
.await
.expect("binary get");
assert_eq!(served.data.as_ref(), b"tiny-png");
let stored = blob_store.get(&blob_ref.blob_id).await.expect("blob get");
assert_eq!(stored.media_type, "image/png");
assert_eq!(
base64::engine::general_purpose::STANDARD
.decode(stored.data.as_bytes())
.expect("stored blob base64")
.as_slice(),
b"tiny-png"
);
runtime.mob_handle().stop().await.expect("stop");
}