use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
use async_trait::async_trait;
use serde_json::{Value, json};
use crate::activity::cache::ActivityState;
use crate::core::sm::control::{
LaunchParams, RawObservation, SessionControl, SessionControlError, Submit, Summary,
};
type AdoptRecord = (String, String, Option<String>, Option<String>);
pub struct MockSessionControl {
next_id: AtomicUsize,
launches: Mutex<Vec<(String, LaunchParams)>>,
sends: Mutex<Vec<(String, String)>>,
adopts: Mutex<Vec<AdoptRecord>>,
get_body: Mutex<Value>,
}
impl Default for MockSessionControl {
fn default() -> Self {
Self {
next_id: AtomicUsize::new(0),
launches: Mutex::new(Vec::new()),
sends: Mutex::new(Vec::new()),
adopts: Mutex::new(Vec::new()),
get_body: Mutex::new(json!({ "session": { "state": "running" } })),
}
}
}
impl MockSessionControl {
pub fn with_evidence(evidence: &str) -> Self {
let mock = Self::default();
*mock.get_body.lock().expect("lock") =
json!({ "session": { "state": "running", "pane": evidence } });
mock
}
pub fn launches(&self) -> Vec<(String, LaunchParams)> {
self.launches.lock().expect("lock").clone()
}
pub fn sends(&self) -> Vec<(String, String)> {
self.sends.lock().expect("lock").clone()
}
pub fn adopts(&self) -> Vec<AdoptRecord> {
self.adopts.lock().expect("lock").clone()
}
}
#[async_trait]
impl SessionControl for MockSessionControl {
async fn launch(&self, params: LaunchParams) -> Result<Value, SessionControlError> {
let n = self.next_id.fetch_add(1, Ordering::SeqCst) + 1;
let session_id = format!("s-{n}");
self.launches
.lock()
.expect("lock")
.push((session_id.clone(), params));
Ok(json!({ "session_id": session_id }))
}
async fn list(&self) -> Result<Value, SessionControlError> {
Ok(json!({ "sessions": [] }))
}
async fn get(&self, _session_id: &str) -> Result<Value, SessionControlError> {
Ok(self.get_body.lock().expect("lock").clone())
}
async fn send(&self, session_id: &str, text: &str) -> Result<Value, SessionControlError> {
self.sends
.lock()
.expect("lock")
.push((session_id.to_string(), text.to_string()));
Ok(json!({ "ok": true }))
}
async fn stop(&self, _session_id: &str) -> Result<Value, SessionControlError> {
Ok(json!({ "ok": true }))
}
async fn resume(&self, _session_id: &str) -> Result<Value, SessionControlError> {
Ok(json!({ "ok": true }))
}
async fn kill(&self, _session_id: &str) -> Result<Value, SessionControlError> {
Ok(json!({ "ok": true }))
}
async fn inject_text(
&self,
session_id: &str,
text: &str,
_submit: Submit,
) -> Result<(), SessionControlError> {
self.sends
.lock()
.expect("lock")
.push((session_id.to_string(), text.to_string()));
Ok(())
}
async fn observe(
&self,
_session_id: &str,
_lines: usize,
) -> Result<RawObservation, SessionControlError> {
let body = self.get_body.lock().expect("lock").clone();
let raw_pane = body
.get("session")
.and_then(|s| s.get("pane"))
.and_then(|p| p.as_str())
.unwrap_or_default()
.to_string();
Ok(RawObservation {
raw_pane,
runtime_active: true,
pending_decision: None,
proposed_default: None,
})
}
async fn summarize(&self, _session_id: &str) -> Result<Summary, SessionControlError> {
Ok(Summary {
state: ActivityState::Unknown,
summary: None,
confidence: 0.0,
})
}
async fn adopt(
&self,
tmux_name: &str,
cwd: &str,
task: Option<&str>,
runtime: Option<&str>,
) -> Result<Value, SessionControlError> {
let n = self.next_id.fetch_add(1, Ordering::SeqCst) + 1;
self.adopts.lock().expect("lock").push((
tmux_name.to_string(),
cwd.to_string(),
task.map(str::to_string),
runtime.map(str::to_string),
));
Ok(json!({ "session_id": format!("s-{n}") }))
}
}