This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/crates/maestro-daemon/tests/sidecar.rs
T
Miguel Palhas fa846000e7 feat(maestro): persist the agent's own session id on the sidecar (#161)
The Claude Code hook already receives a session_id in every payload, but
the activity route dropped it and the daemon had no field to store it on.
Parse it out of the hook body, add Session.agent_session_id, and write it
through to the sidecar once — the first report that carries one wins, and
later reports never overwrite it since an agent's own id never changes
mid-session.

This is the input to a follow-up POST /api/sessions/:id/resume.
2026-09-04 08:22:51 +01:00

117 lines
3.4 KiB
Rust

//! Round-trip and recovery tests for the on-disk sidecar session store.
mod common;
use std::collections::HashMap;
use std::path::PathBuf;
use chrono::{DateTime, Utc};
use maestro_daemon::sidecar::SidecarStore;
use maestro_protocol::{Session, SessionStatus};
use serde_json::Value;
use tempfile::TempDir;
use uuid::Uuid;
fn sample(id: Uuid) -> Session {
Session {
id,
cwd: PathBuf::from("/tmp/work"),
command: "bash".into(),
env: HashMap::from([("FOO".into(), "bar".into())]),
metadata: HashMap::from([("name".into(), Value::String("maestro-impl".into()))]),
worktree: None,
status: SessionStatus::Running,
created_at: DateTime::parse_from_rfc3339("2026-05-24T12:00:00Z")
.unwrap()
.with_timezone(&Utc),
foreground: None,
activity: Default::default(),
activity_detail: None,
last_activity_at: chrono::Utc::now(),
activity_since: chrono::Utc::now(),
current_cwd: Some(PathBuf::from("/tmp/work/crates")),
blocked_work: None,
last_focused_at: None,
agent_session_id: Some("conv-abc123".into()),
}
}
#[test]
fn write_then_read_round_trip() {
let dir = TempDir::new().unwrap();
let store = SidecarStore::new(dir.path());
let id = Uuid::new_v4();
let s = sample(id);
store.write(&s).expect("write");
let loaded = store.read(id).expect("read");
assert_eq!(loaded.as_ref(), Some(&s));
let path = store.path_for(id);
assert!(path.exists());
assert!(path.ends_with(format!("{id}.json")));
}
#[test]
fn read_missing_returns_none() {
let dir = TempDir::new().unwrap();
let store = SidecarStore::new(dir.path());
assert!(store.read(Uuid::new_v4()).unwrap().is_none());
}
#[test]
fn delete_removes_file_and_is_idempotent() {
let dir = TempDir::new().unwrap();
let store = SidecarStore::new(dir.path());
let id = Uuid::new_v4();
let s = sample(id);
store.write(&s).unwrap();
assert!(store.path_for(id).exists());
store.delete(id).unwrap();
assert!(!store.path_for(id).exists());
// second delete is a no-op
store.delete(id).unwrap();
}
#[test]
fn load_all_recovers_every_sidecar_and_skips_garbage() {
let dir = TempDir::new().unwrap();
let store = SidecarStore::new(dir.path());
let s1 = sample(Uuid::new_v4());
let s2 = sample(Uuid::new_v4());
store.write(&s1).unwrap();
store.write(&s2).unwrap();
// garbage files in the directory should be ignored
std::fs::write(dir.path().join("not-json.txt"), b"hello").unwrap();
std::fs::write(dir.path().join("broken.json"), b"{not json}").unwrap();
let mut loaded = store.load_all().expect("load_all");
loaded.sort_by_key(|s| s.id);
let mut expected = vec![s1, s2];
expected.sort_by_key(|s| s.id);
assert_eq!(loaded, expected);
}
#[test]
fn load_all_returns_empty_when_directory_missing() {
let dir = TempDir::new().unwrap();
let store = SidecarStore::new(dir.path().join("does-not-exist"));
assert!(store.load_all().unwrap().is_empty());
}
#[test]
fn write_creates_parent_directory() {
let dir = TempDir::new().unwrap();
let nested = dir.path().join("a/b/c");
let store = SidecarStore::new(&nested);
let s = sample(Uuid::new_v4());
store.write(&s).expect("write");
assert!(nested.exists());
assert!(store.path_for(s.id).exists());
}