agentmux_srv/backend/blockcontroller/
durable_mode.rs1use crate::backend::obj::{self, MetaMapType};
8
9use super::durable_ssh::META_KEY_DURABLE;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Mode {
14 Plain,
15 Chosen,
21 Default,
25}
26
27pub fn wants(meta: &MetaMapType, config: Option<&crate::backend::wconfig::FullConfigType>) -> bool {
29 mode(meta, config) != Mode::Plain
30}
31
32pub fn mode(meta: &MetaMapType, config: Option<&crate::backend::wconfig::FullConfigType>) -> Mode {
37 let conn = obj::meta_get_string(meta, super::META_KEY_CONNECTION, "");
38 if !matches!(
39 crate::backend::remote::ConnTarget::parse(&conn),
40 Ok(crate::backend::remote::ConnTarget::Ssh(_))
41 ) {
42 return Mode::Plain;
43 }
44 if !obj::meta_get_string(
49 meta,
50 crate::backend::remote::askpass::META_KEY_AGENT_BLOCK,
51 "",
52 )
53 .is_empty()
54 {
55 return Mode::Plain;
56 }
57 if crate::backend::remote::helper_consent::plain_instead(&conn)
62 && !crate::backend::remote::helper_hosts::known(&conn)
63 {
64 return Mode::Plain;
65 }
66 if crate::backend::remote::helper_consent::unsupported_host(&conn) {
69 return Mode::Plain;
70 }
71 let conn_setting = config.and_then(|c| {
72 c.connections
73 .iter()
74 .find(|(name, _)| crate::backend::remote::conn::same_connection(name, &conn))
75 .and_then(|(_, k)| k.term_durable)
76 });
77 resolve_durable(
78 meta.get(META_KEY_DURABLE).and_then(|v| v.as_bool()),
79 conn_setting,
80 config.and_then(|c| c.settings.term_durable),
81 || crate::backend::remote::helper_hosts::known(&conn),
82 || crate::backend::remote::helper_consent::not_installed(&conn),
83 )
84}
85
86pub fn resolve_durable(
90 pane: Option<bool>,
91 connection: Option<bool>,
92 global: Option<bool>,
93 host_has_helper: impl FnOnce() -> bool,
94 helper_not_installed: impl FnOnce() -> bool,
95) -> Mode {
96 match pane.or(connection).or(global) {
97 Some(true) => Mode::Chosen,
98 Some(false) => Mode::Plain,
99 None if host_has_helper() => Mode::Default,
100 None if helper_not_installed() => Mode::Plain,
101 None => Mode::Default,
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
113 fn a_host_the_user_declined_the_helper_on_is_not_durable() {
114 use crate::backend::remote::helper_consent::{install_answering_yes, note_answer, Answer};
115 install_answering_yes();
116 let mut meta = MetaMapType::new();
117 meta.insert(super::super::META_KEY_CONNECTION.into(), serde_json::json!("declined-durable-host"));
118 meta.insert(META_KEY_DURABLE.into(), serde_json::json!(true));
119 assert!(wants(&meta, None));
120
121 let answer = |approve| Answer { answered: true, approve, remember: false };
122 note_answer("declined-durable-host", answer(false));
123 assert!(!wants(&meta, None), "Not now: a plain SSH terminal");
124 note_answer("declined-durable-host", answer(true));
125 assert!(wants(&meta, None), "Install: durable again");
126 }
127
128 #[test]
129 fn only_an_ssh_pane_with_durable_on_is_durable() {
130 let meta = |conn: &str, durable: Option<bool>| {
131 let mut m = MetaMapType::new();
132 m.insert(
133 super::super::META_KEY_CONNECTION.into(),
134 serde_json::json!(conn),
135 );
136 if let Some(d) = durable {
137 m.insert(META_KEY_DURABLE.into(), serde_json::json!(d));
138 }
139 m
140 };
141 assert!(wants(&meta("area54", Some(true)), None));
142 assert!(!wants(&meta("area54", Some(false)), None));
143 assert!(!wants(&meta("local", Some(true)), None));
144 assert!(!wants(&meta("wsl://Ubuntu", Some(true)), None));
145
146 let mut config = crate::backend::wconfig::FullConfigType::default();
148 config.settings.term_durable = Some(false);
149 config.connections.insert(
150 "durable-test-host".into(),
151 crate::backend::wconfig::ConnKeywords {
152 term_durable: Some(true),
153 ..Default::default()
154 },
155 );
156 assert!(wants(&meta("durable-test-host", None), Some(&config)));
157 assert!(!wants(
158 &meta("durable-test-host", Some(false)),
159 Some(&config)
160 ));
161 assert!(!wants(&meta("durable-test-other", None), Some(&config)));
162
163 let mut agent = meta("durable-test-host", Some(true));
165 agent.insert(
166 crate::backend::remote::askpass::META_KEY_AGENT_BLOCK.into(),
167 serde_json::json!("agent-block"),
168 );
169 assert!(!wants(&agent, Some(&config)));
170 }
171
172 #[test]
173 fn durable_is_the_pane_then_the_connection_then_global_then_the_helper() {
174 let never = || panic!("asked the host only when nothing is set");
175 assert_eq!(resolve_durable(Some(true), Some(false), Some(false), never, never), Mode::Chosen);
176 assert_eq!(resolve_durable(None, Some(false), Some(true), never, never), Mode::Plain);
177 assert_eq!(resolve_durable(None, None, Some(true), never, never), Mode::Chosen);
178 assert_eq!(resolve_durable(None, None, None, || true, never), Mode::Default);
179 assert_eq!(resolve_durable(None, None, None, || false, || false), Mode::Default);
182 assert_eq!(resolve_durable(None, None, None, || false, || true), Mode::Plain);
183 }
184
185 #[test]
186 fn a_new_hosts_terminal_offers_durable_by_default() {
187 let mut meta = MetaMapType::new();
188 meta.insert(super::super::META_KEY_CONNECTION.into(), serde_json::json!("me@offered-host"));
189 assert_eq!(mode(&meta, None), Mode::Default);
190 assert!(wants(&meta, None));
191 }
192}