use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{Mutex, watch};
use crate::brain::provider::Provider;
use crate::channels::ChannelFactory;
use crate::config::Config;
use crate::db::Database;
use crate::services::ServiceContext;
use crate::tests::agent_service_mocks::{
MockProvider, MockProviderWithTools, create_test_service_with_provider,
};
#[tokio::test]
async fn reload_clears_a_chain_that_config_no_longer_declares() {
let (svc, _sid) = create_test_service_with_provider(Arc::new(MockProvider)).await;
svc.set_fallback_providers_for_test(vec![Arc::new(MockProviderWithTools::new())]);
assert!(
svc.has_fallback_provider(),
"precondition: the runtime is holding a chain"
);
svc.reload_fallback_providers(&Config::default()).await;
assert!(
!svc.has_fallback_provider(),
"#1249: a provider removed from config must stop receiving traffic \
without a restart"
);
assert!(svc.fallback_chain_snapshot().is_empty());
}
#[tokio::test]
async fn snapshot_reflects_the_current_chain() {
let (svc, _sid) = create_test_service_with_provider(Arc::new(MockProvider)).await;
assert!(
svc.fallback_chain_snapshot().is_empty(),
"test config carries no fallbacks"
);
svc.set_fallback_providers_for_test(vec![Arc::new(MockProviderWithTools::new())]);
assert_eq!(svc.fallback_chain_snapshot().len(), 1);
svc.set_fallback_providers_for_test(vec![]);
assert!(svc.fallback_chain_snapshot().is_empty());
}
#[tokio::test]
async fn session_swap_wraps_the_reloaded_chain() {
let (svc, sid) = create_test_service_with_provider(Arc::new(MockProvider)).await;
svc.set_fallback_providers_for_test(vec![Arc::new(MockProviderWithTools::new())]);
svc.swap_provider_for_session(sid, Arc::new(MockProvider), "mock-model");
assert!(
svc.provider_for_session(sid).is_fallback_chain(),
"precondition: a chain exists, so the swap wraps"
);
svc.reload_fallback_providers(&Config::default()).await;
svc.swap_provider_for_session(sid, Arc::new(MockProvider), "mock-model");
assert!(
!svc.provider_for_session(sid).is_fallback_chain(),
"#1249: with the chain removed there is nothing to wrap — the session \
must not keep cascading into providers the config no longer lists"
);
}
async fn test_factory() -> ChannelFactory {
let db = Database::connect_in_memory().await.unwrap();
let context = ServiceContext::new(db.pool().clone());
let (_, config_rx) = watch::channel(Config::default());
ChannelFactory::new(
Arc::new(MockProvider) as Arc<dyn Provider>,
context,
"test brain".to_string(),
PathBuf::from("/tmp"),
PathBuf::from("/tmp/oc_test_brain_1700"),
Arc::new(Mutex::new(None)),
config_rx,
)
}
#[tokio::test]
async fn factory_reload_clears_a_live_agent_chain() {
let factory = test_factory().await;
let service = factory.create_agent_service_full(None, None).await;
service.set_fallback_providers_for_test(vec![Arc::new(MockProviderWithTools::new())]);
assert!(
service.has_fallback_provider(),
"precondition: the built agent is holding a chain"
);
factory.reload_providers(&Config::default(), None).await;
assert!(
!service.has_fallback_provider(),
"#1700: a factory-built agent must drop a provider the config no longer \
declares, without a restart"
);
}
#[tokio::test]
async fn factory_reload_swaps_a_live_agent_primary() {
let factory = test_factory().await;
let service = factory.create_agent_service_full(None, None).await;
assert_eq!(
service.provider_name(),
"mock",
"precondition: the factory starts on its ctor provider"
);
factory
.reload_providers(
&Config::default(),
Some(Arc::new(MockProviderWithTools::new()) as Arc<dyn Provider>),
)
.await;
assert_eq!(
service.provider_name(),
"mock-with-tools",
"#1700: the live agent was handed the rebuilt primary"
);
}
#[tokio::test]
async fn agents_built_after_a_reload_get_the_reloaded_primary() {
let factory = test_factory().await;
factory
.reload_providers(
&Config::default(),
Some(Arc::new(MockProviderWithTools::new()) as Arc<dyn Provider>),
)
.await;
let after = factory.create_agent_service_full(None, None).await;
assert_eq!(
after.provider_name(),
"mock-with-tools",
"#1700: the provider slot must not stay frozen at the ctor instance"
);
}
#[tokio::test]
async fn reload_prunes_dropped_agents_and_still_reloads_the_rest() {
let factory = test_factory().await;
let transient = factory.create_agent_service_full(None, None).await;
let retained = factory.create_agent_service_full(None, None).await;
drop(transient);
factory
.reload_providers(
&Config::default(),
Some(Arc::new(MockProviderWithTools::new()) as Arc<dyn Provider>),
)
.await;
assert_eq!(
retained.provider_name(),
"mock-with-tools",
"#1700: one dropped channel must not shield the live ones from a reload"
);
}