use super::*;
use crate::app::orchestrator::engines::{RESTART_BUDGET, Server};
use crate::shared::config::{CloudProvider, ServerMode};
use crate::shared::secrets::{ExternalSlot, SecretKey};
use crate::shared::server::ServerStatus;
#[tokio::test]
async fn bootstrap_emits_settings_snapshot() {
let (_d, cmd_tx, mut evt_rx, handle) = spawn_orch(None);
let ev = wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
if let AppEvent::Settings {
config, profiles, ..
} = ev
{
assert_eq!(config.schema_version, AppConfig::default().schema_version);
assert_eq!(profiles.len(), 1, "the default profile is in the snapshot");
}
drop(cmd_tx);
handle.await.unwrap();
}
#[tokio::test]
async fn update_config_persists_and_reemits_settings() {
let (_d, cmd_tx, mut evt_rx, handle) = spawn_orch(None);
let root = _d.path().to_path_buf();
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
let config = AppConfig {
max_tool_rounds: 3,
..Default::default()
};
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(config)))
.unwrap();
wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { config, .. } if config.max_tool_rounds == 3),
)
.await
.unwrap();
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
let reopened = Storage::open(Paths::with_root(&root)).unwrap();
assert_eq!(reopened.json().load_config().unwrap().max_tool_rounds, 3);
}
#[tokio::test]
async fn update_profile_persists_edit() {
let (_d, cmd_tx, mut evt_rx, handle) = spawn_orch(None);
let ev = wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
let id = match ev {
AppEvent::Settings { profiles, .. } => profiles[0].id,
_ => unreachable!(),
};
cmd_tx
.send(AppCommand::UpdateProfile {
id,
edit: Box::new(ProfileEdit {
system_message: Some("новое sys".into()),
..Default::default()
}),
})
.unwrap();
wait_for(&mut evt_rx, |e| {
matches!(e, AppEvent::Settings { profiles, .. }
if profiles.iter().any(|p| p.default_system_message == "новое sys"))
})
.await
.unwrap();
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
}
#[tokio::test(start_paused = true)]
async fn model_change_restarts_chat_server_debounced() {
let backend = Arc::new(MockBackend::scripted(vec![ChatChunk::Finished(
FinishReason::Stop,
)])) as Arc<dyn EngineBackend>;
let sup = Arc::new(MockSupervisor::with_backend(Some(backend)));
let dir = tempfile::tempdir().unwrap();
let storage = Arc::new(Storage::open(Paths::with_root(dir.path())).unwrap());
let (cmd_tx, cmd_rx) = unbounded_channel();
let (evt_tx, mut evt_rx) = unbounded_channel();
let handle = tokio::spawn(run(OrchestratorDeps {
cmd_rx,
evt_tx,
storage,
config: AppConfig::default(),
supervisor: sup.clone(),
default_language: crate::shared::i18n::Lang::default(),
extra_tools: Vec::new(),
}));
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
assert_eq!(sup.chat_call_count(), 1);
let engine1 = crate::shared::config::EngineSettings {
managed: crate::shared::config::ManagedSettings {
model_path: Some("other.gguf".into()),
..Default::default()
},
..Default::default()
};
let mut engine2 = engine1.clone();
engine2.managed.gpu_layers = 10;
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(AppConfig {
engine: engine1,
..Default::default()
})))
.unwrap();
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(AppConfig {
engine: engine2,
..Default::default()
})))
.unwrap();
wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { config, .. } if config.engine.managed.gpu_layers == 10),
)
.await
.unwrap();
assert_eq!(
sup.chat_call_count(),
1,
"the restart is deferred by the debounce, the config is applied right away"
);
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::ServerStatus(_)))
.await
.unwrap();
assert_eq!(
sup.chat_call_count(),
2,
"two engine edits → one deferred server restart"
);
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
}
#[tokio::test(start_paused = true)]
async fn an_edit_and_its_undo_cost_no_restart() {
let backend = Arc::new(MockBackend::scripted(vec![ChatChunk::Finished(
FinishReason::Stop,
)])) as Arc<dyn EngineBackend>;
let sup = Arc::new(MockSupervisor::with_backend(Some(backend)));
let dir = tempfile::tempdir().unwrap();
let storage = Arc::new(Storage::open(Paths::with_root(dir.path())).unwrap());
let (cmd_tx, cmd_rx) = unbounded_channel();
let (evt_tx, mut evt_rx) = unbounded_channel();
let handle = tokio::spawn(run(OrchestratorDeps {
cmd_rx,
evt_tx,
storage,
config: AppConfig::default(),
supervisor: sup.clone(),
default_language: crate::shared::i18n::Lang::default(),
extra_tools: Vec::new(),
}));
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
assert_eq!(sup.chat_call_count(), 1, "bootstrap raised it once");
let edited = crate::shared::config::EngineSettings {
managed: crate::shared::config::ManagedSettings {
model_path: Some("other.gguf".into()),
..Default::default()
},
..Default::default()
};
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(AppConfig {
engine: edited,
..Default::default()
})))
.unwrap();
cmd_tx
.send(AppCommand::UpdateConfig(Box::default()))
.unwrap();
wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { config, .. } if config.engine == AppConfig::default().engine),
)
.await
.unwrap();
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
assert_eq!(
sup.chat_call_count(),
1,
"the config came back to what the server is already running — nothing to do"
);
let changed = crate::shared::config::EngineSettings {
managed: crate::shared::config::ManagedSettings {
gpu_layers: 10,
..Default::default()
},
..Default::default()
};
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(AppConfig {
engine: changed,
..Default::default()
})))
.unwrap();
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::ServerStatus(_)))
.await
.unwrap();
assert_eq!(
sup.chat_call_count(),
2,
"exactly one restart, for the change that actually differed"
);
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn set_api_key_persists_encrypted_and_reads_back() {
if !crate::shared::secrets::scheme_available() {
return; }
let (_d, cmd_tx, mut evt_rx, handle) = spawn_orch(None);
let root = _d.path().to_path_buf();
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
cmd_tx
.send(AppCommand::SetSecret {
key: SecretKey::Provider(CloudProvider::OpenAi),
value: "sk-super-secret-42".into(),
})
.unwrap();
wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { secrets_present, .. } if !secrets_present.is_empty()),
)
.await
.unwrap();
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
let raw = std::fs::read_to_string(root.join("settings.json")).unwrap();
assert!(
!raw.contains("sk-super-secret-42"),
"the key's plaintext leaked into settings.json"
);
assert!(raw.contains("api_keys"), "the key entry wasn't saved");
let reopened = Storage::open(Paths::with_root(&root)).unwrap();
let cfg = reopened.json().load_config().unwrap();
assert_eq!(
crate::shared::secrets::stored_key(&cfg.api_keys, CloudProvider::OpenAi.key()).as_deref(),
Some("sk-super-secret-42")
);
}
#[tokio::test(start_paused = true)]
async fn set_external_key_reraises_that_server_with_the_key() {
if !crate::shared::secrets::scheme_available() {
return; }
let config = AppConfig {
engine: crate::shared::config::EngineSettings {
mode: ServerMode::External,
external: crate::shared::config::ExternalSettings {
url: Some("http://127.0.0.1:9/v1".into()),
..Default::default()
},
..Default::default()
},
..Default::default()
};
let (_d, sup, cmd_tx, mut evt_rx, handle) = spawn_orch_sup(config);
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
assert_eq!(sup.chat_call_count(), 1, "bootstrap raised it once");
assert_eq!(
sup.chat_keys(),
vec![None],
"nothing is stored yet, so the supervisor falls back to env"
);
cmd_tx
.send(AppCommand::SetSecret {
key: SecretKey::External(ExternalSlot::Chat),
value: "sk-gateway-1".into(),
})
.unwrap();
wait_for(&mut evt_rx, |e| {
matches!(e, AppEvent::Settings { secrets_present, .. }
if secrets_present.contains(&SecretKey::External(ExternalSlot::Chat)))
})
.await
.unwrap();
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::ServerStatus(_)))
.await
.unwrap();
assert_eq!(
sup.chat_keys(),
vec![None, Some("sk-gateway-1".into())],
"the chat server came back up with the external slot's key"
);
for slot in [ExternalSlot::Tts, ExternalSlot::Embed] {
cmd_tx
.send(AppCommand::SetSecret {
key: SecretKey::External(slot),
value: format!("sk-{slot:?}-2"),
})
.unwrap();
wait_for(&mut evt_rx, |e| {
matches!(e, AppEvent::Settings { secrets_present, .. }
if secrets_present.contains(&SecretKey::External(slot)))
})
.await
.unwrap();
}
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::ServerStatus(_)))
.await
.unwrap();
assert_eq!(
sup.chat_call_count(),
2,
"neither the speech nor the embedding key may restart the chat server"
);
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn set_backup_password_persists_encrypted_and_reads_back() {
if !crate::shared::secrets::scheme_available() {
return; }
let (_d, cmd_tx, mut evt_rx, handle) = spawn_orch(None);
let root = _d.path().to_path_buf();
wait_for(&mut evt_rx, |e| {
matches!(e, AppEvent::Settings { secrets_present, .. } if !secrets_present.contains(&SecretKey::BackupPassword))
})
.await
.unwrap();
cmd_tx
.send(AppCommand::SetSecret {
key: SecretKey::BackupPassword,
value: "open-sesame-42".into(),
})
.unwrap();
wait_for(&mut evt_rx, |e| {
matches!(e, AppEvent::Settings { secrets_present, .. } if secrets_present.contains(&SecretKey::BackupPassword))
})
.await
.unwrap();
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
let raw = std::fs::read_to_string(root.join("settings.json")).unwrap();
assert!(
!raw.contains("open-sesame-42"),
"the backup password leaked into settings.json in the clear"
);
let reopened = Storage::open(Paths::with_root(&root)).unwrap();
let cfg = reopened.json().load_config().unwrap();
assert_eq!(
crate::shared::secrets::stored_key(
&cfg.api_keys,
crate::shared::secrets::BACKUP_PASSWORD_KEY
)
.as_deref(),
Some("open-sesame-42")
);
}
#[tokio::test]
async fn update_config_preserves_stored_api_keys() {
if !crate::shared::secrets::scheme_available() {
return;
}
let (_d, cmd_tx, mut evt_rx, handle) = spawn_orch(None);
let root = _d.path().to_path_buf();
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
cmd_tx
.send(AppCommand::SetSecret {
key: SecretKey::Provider(CloudProvider::Claude),
value: "sk-ant-keep-me".into(),
})
.unwrap();
wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { secrets_present, .. } if !secrets_present.is_empty()),
)
.await
.unwrap();
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(AppConfig {
max_tool_rounds: 5,
..Default::default()
})))
.unwrap();
wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { config, .. } if config.max_tool_rounds == 5),
)
.await
.unwrap();
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
let reopened = Storage::open(Paths::with_root(&root)).unwrap();
let cfg = reopened.json().load_config().unwrap();
assert_eq!(
crate::shared::secrets::stored_key(&cfg.api_keys, CloudProvider::Claude.key()).as_deref(),
Some("sk-ant-keep-me"),
"editing settings erased the saved key"
);
}
#[tokio::test]
async fn set_empty_api_key_removes_stored_entry() {
if !crate::shared::secrets::scheme_available() {
return;
}
let (_d, cmd_tx, mut evt_rx, handle) = spawn_orch(None);
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
cmd_tx
.send(AppCommand::SetSecret {
key: SecretKey::Provider(CloudProvider::Gemini),
value: "sk-temp".into(),
})
.unwrap();
wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { secrets_present, .. } if !secrets_present.is_empty()),
)
.await
.unwrap();
cmd_tx
.send(AppCommand::SetSecret {
key: SecretKey::Provider(CloudProvider::Gemini),
value: String::new(),
})
.unwrap();
wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { secrets_present, .. } if secrets_present.is_empty()),
)
.await
.unwrap();
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn settings_snapshot_carries_flags_not_secrets() {
if !crate::shared::secrets::scheme_available() {
return;
}
let (_d, cmd_tx, mut evt_rx, handle) = spawn_orch(None);
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
cmd_tx
.send(AppCommand::SetSecret {
key: SecretKey::Provider(CloudProvider::OpenAi),
value: "sk-in-snapshot-test".into(),
})
.unwrap();
let ev = wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { secrets_present, .. } if !secrets_present.is_empty()),
)
.await
.unwrap();
if let AppEvent::Settings {
config,
secrets_present,
..
} = ev
{
assert_eq!(
secrets_present,
vec![SecretKey::Provider(CloudProvider::OpenAi)]
);
assert!(
config.api_keys.is_empty(),
"the UI snapshot must not carry key entries"
);
}
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn dead_managed_server_is_relaunched_until_the_budget_runs_out() {
let (_d, mut orch) = bare_orch();
orch.config.engine.mode = ServerMode::Managed;
for round in 0..RESTART_BUDGET {
orch.engines
.set_chat_status(ServerStatus::Disconnected("process gone".into()));
orch.relaunch_dead_managed_servers();
assert!(
!matches!(
orch.engines.status_of(Server::Chat),
ServerStatus::Disconnected(_)
),
"round {round}: a dead managed server should have been relaunched"
);
}
orch.engines
.set_chat_status(ServerStatus::Disconnected("process gone".into()));
orch.relaunch_dead_managed_servers();
assert!(
matches!(
orch.engines.status_of(Server::Chat),
ServerStatus::Disconnected(_)
),
"the crash-loop guard should stop relaunching"
);
}
#[tokio::test]
async fn a_refused_launch_is_not_relaunched() {
let (_d, mut orch) = bare_orch();
let supervisor = Arc::new(MockSupervisor::refusing_chat("port 8000 is taken"));
orch.engines = EngineManager::new(
supervisor.clone(),
unbounded_channel().0,
unbounded_channel().0,
unbounded_channel().0,
);
orch.config.engine.mode = ServerMode::Managed;
orch.apply_chat_settings();
assert_eq!(supervisor.chat_call_count(), 1);
assert!(matches!(
orch.engines.status_of(Server::Chat),
ServerStatus::Disconnected(_)
));
for _ in 0..RESTART_BUDGET + 1 {
orch.relaunch_dead_managed_servers(); }
assert_eq!(supervisor.chat_call_count(), 1, "a refusal was retried");
orch.handle_chat_status(ServerStatus::Disconnected("process gone".into()));
assert_eq!(
supervisor.chat_call_count(),
2,
"the control: a monitor's report of a death is relaunched"
);
}
#[tokio::test]
async fn external_server_is_never_relaunched() {
let (_d, mut orch) = bare_orch();
orch.config.engine.mode = ServerMode::External;
orch.engines
.set_chat_status(ServerStatus::Disconnected("host down".into()));
orch.relaunch_dead_managed_servers();
assert!(
matches!(
orch.engines.status_of(Server::Chat),
ServerStatus::Disconnected(_)
),
"an external server must not be relaunched by us"
);
}
#[test]
fn a_stored_search_key_beats_the_named_environment_variable() {
use crate::shared::secrets::SearchSlot;
const VAR: &str = "MINDFORK_TEST_TAVILY_KEY_ENV";
unsafe { std::env::set_var(VAR, "from-the-environment") };
let mut cfg = crate::shared::config::AppConfig::default();
cfg.tools.web_tavily_key_env = Some(VAR.into());
assert_eq!(
super::super::web_search_keys(&cfg),
vec![(SearchSlot::Tavily, "from-the-environment".to_string())]
);
crate::shared::secrets::put_key(
&mut cfg.api_keys,
&crate::shared::secrets::SecretKey::Search(SearchSlot::Tavily).storage_name(),
"from-settings",
|| "test".to_string(),
)
.expect("the machine key scheme must be available in tests");
assert_eq!(
super::super::web_search_keys(&cfg),
vec![(SearchSlot::Tavily, "from-settings".to_string())]
);
unsafe { std::env::remove_var(VAR) };
}
#[test]
fn an_unconfigured_search_provider_yields_no_key() {
let mut cfg = crate::shared::config::AppConfig::default();
cfg.tools.web_tavily_key_env = None;
assert!(super::super::web_search_keys(&cfg).is_empty());
cfg.tools.web_tavily_key_env = Some(" ".into());
assert!(super::super::web_search_keys(&cfg).is_empty());
}
#[tokio::test]
async fn a_stored_search_key_shows_as_present() {
use crate::shared::secrets::SearchSlot;
if !crate::shared::secrets::scheme_available() {
return; }
let (_d, cmd_tx, mut evt_rx, handle) = spawn_orch(None);
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
for slot in SearchSlot::ALL {
cmd_tx
.send(AppCommand::SetSecret {
key: SecretKey::Search(slot),
value: format!("key-for-{slot:?}"),
})
.unwrap();
wait_for(&mut evt_rx, |e| {
matches!(e, AppEvent::Settings { secrets_present, .. }
if secrets_present.contains(&SecretKey::Search(slot)))
})
.await
.unwrap_or_else(|| panic!("{slot:?} never showed as present"));
}
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn what_the_previous_engine_said_does_not_outlive_a_settings_edit() {
use crate::app::orchestrator::compaction::EngineFacts;
use crate::shared::api::contract::ModelCapabilities;
let (_d, mut orch) = bare_orch();
orch.config.engine.mode = ServerMode::External;
let epoch = orch.context.epoch();
orch.handle_budget_result(
epoch,
EngineFacts {
budget: None,
caps: Some(ModelCapabilities {
context_length: Some(64_000),
sampling_fields: Some(vec!["temperature".to_string()].into()),
}),
server: Default::default(),
},
);
assert_eq!(orch.context_budget(), Some(64_000));
assert!(orch.endpoint_catalogued());
orch.config.engine.mode = ServerMode::Claude;
orch.restarts.mark_chat();
orch.flush_restarts();
assert_eq!(
orch.context_budget(),
None,
"the window belonged to the engine that is gone"
);
assert!(!orch.endpoint_catalogued());
assert!(orch.endpoint_sampling_fields().is_none());
assert!(
orch.context.epoch() > epoch,
"and an answer still in flight for it would be dropped"
);
}
struct Recording {
asked: std::sync::Mutex<Vec<String>>,
}
#[async_trait::async_trait]
impl Embedder for Recording {
async fn embed(
&self,
texts: Vec<String>,
_role: crate::shared::api::EmbedRole,
) -> anyhow::Result<Vec<Vec<f32>>> {
let vectors = texts.iter().map(|t| vec![t.len() as f32, 1.0]).collect();
self.asked.lock().unwrap().extend(texts);
Ok(vectors)
}
}
fn orch_on(embedder: Arc<Recording>) -> (tempfile::TempDir, Orchestrator) {
let (dir, mut orch) = bare_orch();
orch.engines = EngineManager::new(
Arc::new(MockSupervisor::with_backend_and_embedder(
None,
Some(embedder as Arc<dyn Embedder>),
)),
unbounded_channel().0,
unbounded_channel().0,
unbounded_channel().0,
);
orch.config.embed.convention = crate::shared::embed_prefix::EmbedConvention::E5;
(dir, orch)
}
async fn embedded(orch: &Orchestrator, seen: &Recording, text: &str) -> Vec<String> {
orch.engines
.embedder()
.embed(
vec![text.to_string()],
crate::shared::api::EmbedRole::Passage,
)
.await
.expect("an embedding");
std::mem::take(&mut *seen.asked.lock().unwrap())
}
fn canary() -> String {
format!("passage: {}", crate::shared::embed_identity::CANARY_TEXT)
}
#[tokio::test]
async fn an_embedder_is_dressed_by_every_road_that_installs_one() {
let seen = Arc::new(Recording {
asked: Default::default(),
});
let (_d, mut orch) = orch_on(seen.clone());
orch.apply_embed_settings();
let asked = embedded(&orch, &seen, "first").await;
assert_eq!(asked.last().map(String::as_str), Some("passage: first"));
assert!(asked.contains(&canary()), "the guard asked: {asked:?}");
assert_eq!(embedded(&orch, &seen, "second").await, ["passage: second"]);
let mut edited = orch.config.clone();
edited.embed.external.model_name = Some("another-model".into());
orch.handle_update_config(edited);
orch.flush_restarts();
let asked = embedded(&orch, &seen, "third").await;
assert_eq!(
asked.last().map(String::as_str),
Some("passage: third"),
"the convention survives the edit"
);
assert!(
asked.contains(&canary()),
"and the guard is armed for the embedder the edit installed: {asked:?}"
);
let mut edited = orch.config.clone();
edited.embed.convention = crate::shared::embed_prefix::EmbedConvention::None;
orch.handle_update_config(edited);
orch.flush_restarts();
let asked = embedded(&orch, &seen, "fourth").await;
assert_eq!(asked.last().map(String::as_str), Some("fourth"));
assert!(
asked.contains(&crate::shared::embed_identity::CANARY_TEXT.to_string()),
"{asked:?}"
);
}
#[tokio::test]
async fn a_relaunched_embedder_is_dressed() {
let seen = Arc::new(Recording {
asked: Default::default(),
});
let (_d, mut orch) = orch_on(seen.clone());
orch.config.embed.mode = ServerMode::Managed;
orch.apply_embed_settings();
embedded(&orch, &seen, "before").await;
orch.engines
.set_embed_status(ServerStatus::Disconnected("the process exited".into()));
orch.relaunch_dead_managed_servers();
let asked = embedded(&orch, &seen, "after").await;
assert_eq!(asked.last().map(String::as_str), Some("passage: after"));
assert!(asked.contains(&canary()), "{asked:?}");
}
#[tokio::test]
async fn the_gateways_switch_reaches_impersonation_and_the_embedder() {
use crate::shared::config::ImpersonationMode;
let (_d, mut orch) = bare_orch();
let mut config = orch.config.clone();
config.impersonation_engine.mode = ImpersonationMode::OpenRouter;
config.embed.mode = ServerMode::OpenRouter;
orch.handle_update_config(config.clone());
orch.flush_restarts();
let current = |orch: &Orchestrator, config: &AppConfig| {
(
orch.engines.impersonation_is_current(
&config.impersonation_engine,
&config.api_keys,
&config.openrouter,
),
orch.engines
.embed_is_current(&config.embed, &config.api_keys, &config.openrouter),
)
};
assert_eq!(current(&orch, &config), (true, true));
config.openrouter.attribution = false;
orch.handle_update_config(config.clone());
assert_eq!(
current(&orch, &config),
(false, false),
"what they were applied with is not what the settings say now"
);
assert_eq!(
orch.restarts.take(),
(false, true, true, false),
"the embedder and impersonation, and not the chat: its mode is not the gateway's"
);
orch.restarts.mark_embed();
orch.restarts.mark_impersonation();
orch.flush_restarts();
assert_eq!(current(&orch, &config), (true, true));
let mut local = config.clone();
local.impersonation_engine.mode = ImpersonationMode::Shared;
local.embed.mode = ServerMode::Managed;
orch.handle_update_config(local.clone());
orch.flush_restarts();
local.openrouter.attribution = true;
orch.handle_update_config(local.clone());
assert_eq!(orch.restarts.take(), (false, false, false, false));
assert_eq!(current(&orch, &local), (true, true));
}
#[test]
fn the_model_list_names_the_app_where_the_gateways_switch_says_so() {
use crate::shared::api::catalogue::ModelSlot;
use crate::shared::config::{CloudSettings, EmbedSettings, ImpersonationMode};
let (_d, mut orch) = bare_orch();
orch.config.engine.mode = ServerMode::OpenRouter;
orch.config.impersonation_engine.mode = ImpersonationMode::OpenRouter;
orch.config.embed = EmbedSettings {
mode: ServerMode::OpenRouter,
..Default::default()
};
let slots = [
ModelSlot::Assistant,
ModelSlot::Impersonation,
ModelSlot::Embedder,
];
let named = |orch: &Orchestrator| {
slots.map(|slot| orch.catalogue_request(slot).expect("a request").attribution)
};
assert_eq!(named(&orch), [true; 3], "on until somebody turns it off");
orch.config.openrouter.attribution = false;
assert_eq!(named(&orch), [false; 3]);
orch.config.openrouter.attribution = true;
orch.config.engine.mode = ServerMode::Grok;
orch.config.engine.grok = CloudSettings {
api_key_env: Some("PATH".into()),
..Default::default()
};
let grok = orch
.catalogue_request(ModelSlot::Assistant)
.expect("a request");
assert!(!grok.attribution);
}
#[test]
fn the_speech_slots_list_is_the_gateways_and_follows_the_slots_own_section() {
use crate::shared::api::catalogue::{CatalogueError, CatalogueShape, ModelSlot};
use crate::shared::config::TtsMode;
let (_d, mut orch) = bare_orch();
orch.config.engine.mode = ServerMode::Managed;
for mode in TtsMode::ALL {
orch.config.tts.mode = mode;
let asked = orch.catalogue_request(ModelSlot::Speech);
if mode != TtsMode::OpenRouter {
assert_eq!(asked, Err(CatalogueError::NotConfigured), "{mode:?}");
continue;
}
let asked = asked.expect("a request");
assert_eq!(asked.shape, CatalogueShape::OpenRouterSpeech);
assert_eq!(asked.base, "https://openrouter.ai/api/v1");
assert_eq!(asked.key, None, "the list is public: asked without a key");
assert!(asked.attribution, "on until somebody turns it off");
}
orch.config.tts.mode = TtsMode::OpenRouter;
orch.config.openrouter.attribution = false;
orch.config.tts.openrouter.url = Some(" https://eu.openrouter.ai/api/v1 ".into());
orch.config.tts.openrouter.api_key_env = Some("PATH".into());
orch.config.engine.openrouter.url = Some("https://chat.example/v1".into());
let asked = orch
.catalogue_request(ModelSlot::Speech)
.expect("a request");
assert_eq!(asked.base, "https://eu.openrouter.ai/api/v1");
assert!(asked.key.is_some(), "the variable the speech section names");
assert!(!asked.attribution);
}
#[tokio::test]
async fn an_answer_of_the_engine_that_is_gone_tells_the_screens_nothing() {
use crate::app::orchestrator::compaction::EngineFacts;
use crate::shared::api::contract::ModelCapabilities;
let answer = || EngineFacts {
budget: None,
caps: Some(ModelCapabilities {
context_length: Some(64_000),
sampling_fields: Some(vec!["temperature".to_string()].into()),
}),
server: Default::default(),
};
let told = |rx: &mut UnboundedReceiver<AppEvent>| {
let mut lists = Vec::new();
while let Ok(event) = rx.try_recv() {
if let AppEvent::EngineSamplingFields(fields) = event {
lists.push(fields);
}
}
lists
};
let (_d, mut orch, mut rx) = bare_orch_rx();
orch.config.engine.mode = ServerMode::External;
let gone = orch.context.epoch();
orch.refresh_engine_facts();
told(&mut rx);
orch.handle_budget_result(gone, answer());
assert_eq!(told(&mut rx), [], "the engine it describes is gone");
assert_eq!(orch.context_budget(), None);
assert!(orch.endpoint_sampling_fields().is_none());
orch.handle_budget_result(orch.context.epoch(), answer());
assert_eq!(
told(&mut rx),
[Some(vec!["temperature".to_string()].into())]
);
assert_eq!(orch.context_budget(), Some(64_000));
}
async fn raised_again(rx: &mut UnboundedReceiver<AppEvent>) {
tokio::time::timeout(
std::time::Duration::from_secs(60),
wait_for(rx, |e| matches!(e, AppEvent::ServerStatus(_))),
)
.await
.expect("the slot is raised again")
.expect("the event stream");
}
#[tokio::test(start_paused = true)]
async fn the_gateways_own_switch_restarts_only_the_slots_that_speak_to_it() {
let backend = Arc::new(MockBackend::scripted(vec![ChatChunk::Finished(
FinishReason::Stop,
)])) as Arc<dyn EngineBackend>;
let sup = Arc::new(MockSupervisor::with_backend(Some(backend)));
let dir = tempfile::tempdir().unwrap();
let storage = Arc::new(Storage::open(Paths::with_root(dir.path())).unwrap());
let (cmd_tx, cmd_rx) = unbounded_channel();
let (evt_tx, mut evt_rx) = unbounded_channel();
let through_the_gateway = |attribution: bool| AppConfig {
engine: crate::shared::config::EngineSettings {
mode: ServerMode::OpenRouter,
..Default::default()
},
openrouter: crate::shared::config::OpenRouterSettings { attribution },
..Default::default()
};
let handle = tokio::spawn(run(OrchestratorDeps {
cmd_rx,
evt_tx,
storage,
config: through_the_gateway(true),
supervisor: sup.clone(),
default_language: crate::shared::i18n::Lang::default(),
extra_tools: Vec::new(),
}));
wait_for(&mut evt_rx, |e| matches!(e, AppEvent::Settings { .. }))
.await
.unwrap();
assert_eq!(sup.chat_call_count(), 1, "bootstrap raised it once");
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(through_the_gateway(
false,
))))
.unwrap();
raised_again(&mut evt_rx).await;
assert_eq!(
sup.chat_call_count(),
2,
"the client has to be rebuilt without the headers"
);
let managed = |attribution: bool, gpu_layers: i32| AppConfig {
engine: crate::shared::config::EngineSettings {
managed: crate::shared::config::ManagedSettings {
gpu_layers,
..Default::default()
},
..Default::default()
},
openrouter: crate::shared::config::OpenRouterSettings { attribution },
..Default::default()
};
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(managed(false, 99))))
.unwrap();
raised_again(&mut evt_rx).await;
assert_eq!(sup.chat_call_count(), 3);
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(managed(true, 99))))
.unwrap();
wait_for(
&mut evt_rx,
|e| matches!(e, AppEvent::Settings { config, .. } if config.openrouter.attribution),
)
.await
.unwrap();
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
assert_eq!(
sup.chat_call_count(),
3,
"a managed server has nothing to do with the gateway's switch"
);
cmd_tx
.send(AppCommand::UpdateConfig(Box::new(managed(true, 10))))
.unwrap();
raised_again(&mut evt_rx).await;
assert_eq!(sup.chat_call_count(), 4);
cmd_tx.send(AppCommand::Quit).unwrap();
handle.await.unwrap();
}
#[test]
fn the_video_client_is_built_on_the_key_of_the_provider_it_names() {
use crate::app::orchestrator::video_config;
use crate::shared::config::{CloudProvider, VideoProvider};
use crate::shared::secrets::SecretKey;
let (_d, mut orch) = bare_orch();
assert!(video_config(&orch.config).is_none(), "no key anywhere");
orch.handle_set_secret(
SecretKey::Provider(CloudProvider::OpenRouter),
"sk-or-v1-stored".into(),
);
assert!(
video_config(&orch.config).is_none(),
"the gateway's key is not Gemini's"
);
let mut config = orch.config.clone();
config.video.provider = VideoProvider::OpenRouter;
assert!(
video_config(&config).is_none(),
"a key, and no model: the gateway has no default"
);
config.video.openrouter.model_name = Some("google/gemini-3.5-flash".into());
let built = video_config(&config).expect("a model and the provider's key");
assert_eq!(built.provider, VideoProvider::OpenRouter);
assert_eq!(built.model, "google/gemini-3.5-flash");
assert_eq!(built.api_key, "sk-or-v1-stored");
assert_eq!(built.base_url, "https://openrouter.ai/api/v1");
assert!(built.attribution, "on until somebody turns it off");
config.openrouter.attribution = false;
assert!(!video_config(&config).expect("built").attribution);
config.video.provider = VideoProvider::Gemini;
assert!(video_config(&config).is_none(), "Gemini has no key stored");
}
#[test]
fn what_the_video_client_is_built_from_rebuilds_it() {
use crate::shared::config::{CloudProvider, VideoProvider};
use crate::shared::secrets::SecretKey;
let rebuilt = |orch: &mut Orchestrator, change: &dyn Fn(&mut Orchestrator)| {
let before = orch.registry.clone();
change(orch);
!Arc::ptr_eq(&before, &orch.registry)
};
let switch = |orch: &mut Orchestrator| {
let mut edited = orch.config.clone();
edited.openrouter.attribution = !edited.openrouter.attribution;
orch.handle_update_config(edited);
};
let key_of = |provider: CloudProvider| {
move |orch: &mut Orchestrator| {
orch.handle_set_secret(SecretKey::Provider(provider), "a-key".into())
}
};
let (_d, mut orch) = bare_orch();
assert_eq!(orch.config.video.provider, VideoProvider::Gemini);
assert!(!rebuilt(&mut orch, &switch), "Gemini's client names nobody");
assert!(!rebuilt(&mut orch, &key_of(CloudProvider::OpenRouter)));
assert!(rebuilt(&mut orch, &key_of(CloudProvider::Gemini)));
let mut edited = orch.config.clone();
edited.video.provider = VideoProvider::OpenRouter;
let to_the_gateway = |orch: &mut Orchestrator| orch.handle_update_config(edited.clone());
assert!(rebuilt(&mut orch, &to_the_gateway), "the provider changed");
assert!(rebuilt(&mut orch, &switch), "the gateway's client is named");
assert!(rebuilt(&mut orch, &key_of(CloudProvider::OpenRouter)));
assert!(
!rebuilt(&mut orch, &key_of(CloudProvider::Gemini)),
"Gemini's key is not what the gateway's client was built on"
);
}
#[test]
fn the_video_slots_list_is_the_gateways_and_follows_the_slots_own_section() {
use crate::shared::api::catalogue::{CatalogueError, CatalogueShape, ModelSlot};
use crate::shared::config::VideoProvider;
let (_d, mut orch) = bare_orch();
assert_eq!(
orch.catalogue_request(ModelSlot::Video),
Err(CatalogueError::NotConfigured),
"Gemini's own list says nothing of video"
);
orch.config.video.provider = VideoProvider::OpenRouter;
let asked = orch.catalogue_request(ModelSlot::Video).expect("a request");
assert_eq!(asked.shape, CatalogueShape::OpenRouterVideo);
assert_eq!(asked.base, "https://openrouter.ai/api/v1");
assert_eq!(asked.key, None, "the list is public: asked without a key");
assert!(asked.attribution);
orch.config.openrouter.attribution = false;
orch.config.video.openrouter.url = Some(" https://eu.openrouter.ai/api/v1 ".into());
orch.config.video.openrouter.api_key_env = Some("PATH".into());
orch.config.video.url = Some("https://gemini.example/v1beta".into());
orch.config.tts.openrouter.url = Some("https://speech.example/v1".into());
let asked = orch.catalogue_request(ModelSlot::Video).expect("a request");
assert_eq!(asked.base, "https://eu.openrouter.ai/api/v1");
assert!(asked.key.is_some(), "the variable the video section names");
assert!(!asked.attribution);
}