use super::*;
use std::sync::Arc;
use crate::daemon::state::DaemonState;
use crate::runtime::RuntimeKind;
use crate::session_manager::record::{ManagedSessionId, ManagedSessionState};
async fn decode_response(resp: impl axum::response::IntoResponse) -> serde_json::Value {
let resp = resp.into_response();
let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.expect("read body");
serde_json::from_slice(&bytes).unwrap_or(serde_json::Value::Null)
}
async fn seeded_state() -> (Arc<DaemonState>, String) {
let root = tempfile::tempdir().unwrap().keep();
let state = Arc::new(DaemonState::with_root_isolated_managed(root).await);
let mgr = state.session_manager().await;
let record = mgr
.create_with_id(
ManagedSessionId::new(),
"proxy test task".to_string(),
None,
Some("proxytest".to_string()),
None,
None,
None,
RuntimeKind::default(),
false,
false,
)
.await
.expect("seed session");
{
let mut store = mgr.store.write().await;
let mut r = store.get(&record.id).await.unwrap();
r.state = ManagedSessionState::Active;
store.upsert(r).await.unwrap();
}
(state, record.tmux_name)
}
#[tokio::test]
async fn proxy_focus_route_resolves_by_name_and_reports_current() {
let (state, name) = seeded_state().await;
let body = decode_response(
proxy_focus(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyFocusRequest {
conversation_key: "conv-1".into(),
session_id: name.clone(),
}),
)
.await,
)
.await;
assert_eq!(body["outcome"], "focused");
assert_eq!(body["name"], name);
let body = decode_response(
proxy_focus(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyFocusRequest {
conversation_key: "conv-1".into(),
session_id: String::new(),
}),
)
.await,
)
.await;
assert_eq!(body["outcome"], "current");
assert_eq!(body["target"]["name"], name);
}
#[tokio::test]
async fn proxy_get_focus_route_reports_unset() {
let (state, _name) = seeded_state().await;
let body = decode_response(
proxy_get_focus(
axum::extract::State(Arc::clone(&state)),
axum::extract::Path("conv-never-focused".to_string()),
)
.await,
)
.await;
assert_eq!(body["outcome"], "current");
assert!(body["target"].is_null());
}
#[tokio::test]
async fn proxy_focus_route_unknown_target_is_not_found() {
let (state, _name) = seeded_state().await;
let body = decode_response(
proxy_focus(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyFocusRequest {
conversation_key: "conv-1".into(),
session_id: "no-such-session".into(),
}),
)
.await,
)
.await;
assert_eq!(body["outcome"], "not_found");
assert_eq!(body["target"], "no-such-session");
}
#[tokio::test]
async fn proxy_message_route_focused_sends_unfocused_no_focus() {
let (state, name) = seeded_state().await;
let body = decode_response(
proxy_message(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyMessageRequest {
conversation_key: "conv-2".into(),
text: "hello?".into(),
}),
)
.await,
)
.await;
assert_eq!(body["outcome"], "no_focus");
decode_response(
proxy_focus(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyFocusRequest {
conversation_key: "conv-2".into(),
session_id: name.clone(),
}),
)
.await,
)
.await;
let body = decode_response(
proxy_message(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyMessageRequest {
conversation_key: "conv-2".into(),
text: "run the tests".into(),
}),
)
.await,
)
.await;
assert_eq!(body["outcome"], "sent");
assert_eq!(body["target"]["name"], name);
assert_eq!(body["text"], "run the tests");
}
#[tokio::test]
async fn proxy_summary_route_returns_digest_for_focused_session() {
let (state, name) = seeded_state().await;
decode_response(
proxy_focus(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyFocusRequest {
conversation_key: "conv-3".into(),
session_id: name.clone(),
}),
)
.await,
)
.await;
let body = decode_response(
proxy_summary(
axum::extract::State(Arc::clone(&state)),
axum::extract::Path("conv-3".to_string()),
)
.await,
)
.await;
assert_eq!(body["outcome"], "summary");
assert_eq!(body["target"]["name"], name);
assert!(body["state"].is_string());
assert!(
body["summary"]
.as_str()
.unwrap()
.contains("proxy test task")
);
}
#[tokio::test]
async fn proxy_summary_route_no_focus() {
let (state, _name) = seeded_state().await;
let body = decode_response(
proxy_summary(
axum::extract::State(Arc::clone(&state)),
axum::extract::Path("conv-never".to_string()),
)
.await,
)
.await;
assert_eq!(body["outcome"], "no_focus");
}
#[tokio::test]
async fn proxy_unfocus_route_clears_and_reports_none_when_absent() {
let (state, name) = seeded_state().await;
decode_response(
proxy_focus(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyFocusRequest {
conversation_key: "conv-4".into(),
session_id: name.clone(),
}),
)
.await,
)
.await;
let body = decode_response(
proxy_unfocus(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyUnfocusRequest {
conversation_key: "conv-4".into(),
}),
)
.await,
)
.await;
assert_eq!(body["cleared"]["name"], name);
let body = decode_response(
proxy_unfocus(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyUnfocusRequest {
conversation_key: "conv-4".into(),
}),
)
.await,
)
.await;
assert!(body["cleared"].is_null());
}
#[tokio::test]
async fn proxy_message_route_auto_unfocuses_when_session_decommissioned() {
let (state, name) = seeded_state().await;
decode_response(
proxy_focus(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyFocusRequest {
conversation_key: "conv-5".into(),
session_id: name.clone(),
}),
)
.await,
)
.await;
let mgr = state.session_manager().await;
let records = mgr.list().await;
let id = records
.iter()
.find(|r| r.tmux_name == name)
.expect("seeded session present")
.id;
mgr.compact_record(&id)
.await
.expect("compact (hard-remove) seeded session");
let body = decode_response(
proxy_message(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyMessageRequest {
conversation_key: "conv-5".into(),
text: "are you still there?".into(),
}),
)
.await,
)
.await;
assert_eq!(body["outcome"], "auto_unfocused");
let body = decode_response(
proxy_message(
axum::extract::State(Arc::clone(&state)),
axum::Json(ProxyMessageRequest {
conversation_key: "conv-5".into(),
text: "hello again".into(),
}),
)
.await,
)
.await;
assert_eq!(body["outcome"], "no_focus");
}
#[test]
fn parse_managed_id_rejects_non_uuid() {
let err = super::backend::parse_managed_id("not-a-uuid").unwrap_err();
assert!(err.contains("invalid managed session id"));
assert!(
!err.contains("not found"),
"an invalid id must not trip the auto-unfocus 'not found' predicate: {err}"
);
}