use super::*;
use std::sync::Arc;
use crate::client::{
CommandExecutor, FocusOutcome, FocusTarget, InjectOutcome, SessionProxy, SummarizeOutcome,
};
fn offline_proxy() -> SessionProxy {
SessionProxy::new(Arc::new(CommandExecutor::new("http://127.0.0.1:0")))
}
fn target() -> FocusTarget {
FocusTarget {
id: "aaaa1111bbbb2222".into(),
name: "tmpm-api".into(),
}
}
#[test]
fn conv_keys_by_channel_and_thread() {
assert_eq!(conv("C1", None), "C1");
assert_eq!(conv("C1", Some("169.42")), "C1:169.42");
assert_eq!(conv("C1", Some("")), "C1");
}
struct ResolvingBackend;
#[async_trait::async_trait]
impl crate::client::ManagedBackend for ResolvingBackend {
async fn resolve(&self, target: &str) -> Result<(String, String), String> {
Ok((format!("id-{target}"), target.to_string()))
}
async fn send(&self, _id: &str, _text: &str) -> Result<(), String> {
Ok(())
}
async fn activity(&self, _id: &str) -> Result<crate::client::ActivityDigest, String> {
Err("unused".to_string())
}
}
fn resolving_proxy() -> SessionProxy {
SessionProxy::new(Arc::new(ResolvingBackend))
}
#[test]
fn effective_conv_stays_channel_key_with_no_thread() {
let proxy = offline_proxy();
assert_eq!(effective_conv(&proxy, "C1", None), "C1");
}
#[tokio::test]
async fn effective_conv_thread_key_when_neither_focused() {
let proxy = offline_proxy();
assert_eq!(effective_conv(&proxy, "C1", Some("169.42")), "C1:169.42");
}
#[tokio::test]
async fn effective_conv_falls_back_to_channel_when_thread_unfocused() {
let proxy = resolving_proxy();
proxy.focus("C1", "api").await;
assert_eq!(
effective_conv(&proxy, "C1", Some("169.42")),
"C1",
"a threaded reply must fall back to the channel-focused key"
);
}
#[tokio::test]
async fn effective_conv_prefers_thread_when_thread_focused() {
let proxy = resolving_proxy();
proxy.focus("C1:169.42", "api").await;
assert_eq!(effective_conv(&proxy, "C1", Some("169.42")), "C1:169.42");
assert_eq!(effective_conv(&proxy, "C1", Some("999.99")), "C1:999.99");
}
#[test]
fn proxy_verb_classifies_the_three_verbs() {
assert_eq!(proxy_verb("/focus"), Some(ProxyVerb::Focus));
assert_eq!(proxy_verb("unfocus"), Some(ProxyVerb::Unfocus));
assert_eq!(proxy_verb("/SUMMARY"), Some(ProxyVerb::Summary));
assert_eq!(proxy_verb("/fleet"), None);
assert_eq!(proxy_verb(""), None);
}
#[test]
fn render_focus_focused_names_session() {
let body = render_focus(&FocusOutcome::Focused(target()));
assert!(body.contains("Focused on *tmpm-api*"), "{body}");
assert!(body.contains("`/unfocus`"), "{body}");
}
#[test]
fn render_focus_current_none_hints_usage() {
let body = render_focus(&FocusOutcome::Current(None));
assert!(body.contains("Usage"), "{body}");
}
#[test]
fn render_focus_not_found_reports() {
let body = render_focus(&FocusOutcome::NotFound {
target: "no-such".into(),
error: "managed session no-such not found".into(),
});
assert!(body.contains("Cannot focus"), "{body}");
assert!(body.contains("no-such"), "{body}");
}
#[test]
fn render_focus_escapes_hostile_session_name() {
let hostile = FocusTarget {
id: "aaaa1111bbbb2222".into(),
name: "<!channel> pwned".into(),
};
let body = render_focus(&FocusOutcome::Focused(hostile));
assert!(!body.contains("<!channel>"), "{body}");
assert!(body.contains("<!channel> pwned"), "{body}");
let body = render_focus(&FocusOutcome::NotFound {
target: "<!channel>".into(),
error: "managed session <!channel> not found".into(),
});
assert!(!body.contains("<!channel>"), "{body}");
assert!(body.contains("<!channel>"), "{body}");
}
#[test]
fn render_inject_sent_echoes_text() {
let body = render_inject(&InjectOutcome::Sent {
target: target(),
text: "run the tests".into(),
});
assert!(body.contains("tmpm-api"), "{body}");
assert!(body.contains("run the tests"), "{body}");
}
#[test]
fn render_inject_auto_unfocused_signals_gone() {
let body = render_inject(&InjectOutcome::AutoUnfocused {
target: target(),
error: "managed session x not found".into(),
});
assert!(body.contains("gone"), "{body}");
assert!(body.contains("tmpm-api"), "{body}");
}
#[test]
fn render_inject_failed_keeps_focus_prose() {
let body = render_inject(&InjectOutcome::Failed {
target: target(),
error: "connection refused".into(),
});
assert!(body.contains("failed"), "{body}");
assert!(body.contains("Still focused"), "{body}");
}
#[test]
fn render_inject_no_focus_hints() {
let body = render_inject(&InjectOutcome::NoFocus);
assert!(body.contains("No session is focused"), "{body}");
}
#[test]
fn render_inject_escapes_hostile_session_name() {
let hostile = FocusTarget {
id: "aaaa1111bbbb2222".into(),
name: "<!channel> pwned".into(),
};
let body = render_inject(&InjectOutcome::Sent {
target: hostile.clone(),
text: "<!here> urgent".into(),
});
assert!(
!body.contains("<!channel>") && !body.contains("<!here>"),
"{body}"
);
assert!(body.contains("<!channel> pwned"), "{body}");
assert!(body.contains("<!here> urgent"), "{body}");
let body = render_inject(&InjectOutcome::Failed {
target: hostile,
error: "<!channel> connection refused".into(),
});
assert!(!body.contains("<!channel>"), "{body}");
}
#[test]
fn render_summary_ok_shows_state_and_pending() {
let body = render_summary(&SummarizeOutcome::Summary {
target: target(),
state: "active".into(),
summary: "running cargo test".into(),
pending_decision: Some("apply patch?".into()),
});
assert!(body.contains("tmpm-api"), "{body}");
assert!(body.contains("active"), "{body}");
assert!(body.contains("running cargo test"), "{body}");
assert!(body.contains("pending: apply patch?"), "{body}");
}
#[test]
fn render_summary_no_focus_hints() {
let body = render_summary(&SummarizeOutcome::NoFocus);
assert!(body.contains("No session is focused"), "{body}");
}
#[test]
fn render_summary_escapes_hostile_fields() {
let hostile = FocusTarget {
id: "aaaa1111bbbb2222".into(),
name: "<!channel> pwned".into(),
};
let body = render_summary(&SummarizeOutcome::Summary {
target: hostile,
state: "<active>".into(),
summary: "<!here> running cargo test".into(),
pending_decision: Some("<!channel> apply patch?".into()),
});
assert!(
!body.contains("<!channel>") && !body.contains("<!here>") && !body.contains("<active>"),
"{body}"
);
assert!(body.contains("<!channel> pwned"), "{body}");
assert!(body.contains("<active>"), "{body}");
assert!(body.contains("<!here> running cargo test"), "{body}");
assert!(
body.contains("pending: <!channel> apply patch?"),
"{body}"
);
}
#[test]
fn truncate_summary_leaves_short_text_untouched() {
let short = "running cargo test, all green";
assert_eq!(truncate_summary(short).as_ref(), short);
}
#[test]
fn truncate_summary_caps_long_text() {
let long: String = "x".repeat(MAX_SUMMARY_CHARS * 3);
let truncated = truncate_summary(&long);
assert!(
truncated.chars().count() <= MAX_SUMMARY_CHARS + " […truncated]".chars().count(),
"truncated length {} exceeds budget",
truncated.chars().count()
);
assert!(truncated.ends_with("[…truncated]"), "{truncated}");
}
#[tokio::test]
async fn handle_unfocus_when_none() {
let proxy = offline_proxy();
assert!(handle_unfocus(&proxy, "C1").contains("No session was focused"));
}
#[tokio::test]
async fn handle_focus_empty_hints() {
let proxy = offline_proxy();
let body = handle_focus(&proxy, "C1", " ").await;
assert!(body.contains("Usage"), "{body}");
}