test(daemon): event log survives cold daemon restart

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 08:35:38 +07:00
parent a8000323ec
commit 615b90e887
+76
View File
@@ -1163,6 +1163,82 @@ mod tests {
assert_eq!(res_data(&log)["unread"].as_u64().unwrap(), 0); assert_eq!(res_data(&log)["unread"].as_u64().unwrap(), 0);
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn event_log_persists_across_daemon_restart() {
let _serial = crate::test_support::serial();
let dir = tempdir_path();
let state_path = dir.join("state.json");
let sock = dir.join("sock");
let event_id: u64;
let ws_id: String;
// ── Instance A ────────────────────────────────────────────────────────
{
let store: std::sync::Arc<dyn crate::state_store::StateStore> =
std::sync::Arc::new(crate::state_store::JsonStateStore::new(state_path.clone()));
let event_store = make_event_store(&dir);
let sock2 = sock.clone();
tokio::spawn(async move { let _ = serve(&sock2, store, event_store).await; });
wait_for_socket(&sock).await;
let mut s = UnixStream::connect(&sock).await.unwrap();
// Open workspace, spawn surface.
let r = req(&mut s, 1, Cmd::Open { path: std::env::temp_dir().to_string_lossy().into() }).await;
ws_id = res_data(&r)["workspace_id"].as_str().unwrap().to_string();
let r = req(&mut s, 2, Cmd::NewSurface {
workspace_id: spacesh_proto::WorkspaceId(ws_id.clone()),
command: Some("/bin/sh".into()),
args: vec!["-c".into(), "sleep 5".into()],
cols: 80, rows: 24,
}).await;
let sid = res_data(&r)["surface_id"].as_str().unwrap().to_string();
// Drive an Error state → one unread event is logged.
let _ = req(&mut s, 3, Cmd::SetState {
surface_id: spacesh_proto::SurfaceId(sid.clone()),
state: spacesh_proto::status::SurfaceState::Error,
}).await;
// Query and assert unread == 1 before restart.
let log = req(&mut s, 4, Cmd::EventLog { limit: None }).await;
let data = res_data(&log);
assert_eq!(data["unread"].as_u64().unwrap(), 1, "instance A: expected 1 unread event");
assert_eq!(data["events"][0]["kind"].as_str().unwrap(), "error");
assert_eq!(data["events"][0]["workspace_id"].as_str().unwrap(), ws_id);
event_id = data["events"][0]["id"].as_u64().unwrap();
// Wait comfortably longer than the 500 ms debounce so events.json is flushed.
tokio::time::sleep(tokio::time::Duration::from_millis(900)).await;
// Drop `s` (and instance A's task) by falling out of scope.
}
// ── Instance B (same dir, fresh socket path) ──────────────────────────
let sock_b = dir.join("sock2");
let store_b: std::sync::Arc<dyn crate::state_store::StateStore> =
std::sync::Arc::new(crate::state_store::JsonStateStore::new(state_path.clone()));
let event_store_b = make_event_store(&dir);
let sb2 = sock_b.clone();
tokio::spawn(async move { let _ = serve(&sock_b, store_b, event_store_b).await; });
wait_for_socket(&sb2).await;
let mut s2 = UnixStream::connect(&sb2).await.unwrap();
// Query event log on instance B — the persisted event must survive the restart.
let log = req(&mut s2, 1, Cmd::EventLog { limit: None }).await;
let data = res_data(&log);
assert_eq!(data["unread"].as_u64().unwrap(), 1,
"instance B: event log unread count must survive cold restart");
assert_eq!(data["events"][0]["id"].as_u64().unwrap(), event_id,
"instance B: event id must match");
assert_eq!(data["events"][0]["kind"].as_str().unwrap(), "error",
"instance B: event kind must be 'error'");
assert_eq!(data["events"][0]["workspace_id"].as_str().unwrap(), ws_id,
"instance B: workspace_id must match");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn focus_marks_surface_events_read() { async fn focus_marks_surface_events_read() {
let _serial = crate::test_support::serial(); let _serial = crate::test_support::serial();