feat(maestro): serve POST /api/sessions/:id/activity

This commit is contained in:
Miguel Palhas
2026-08-24 21:38:42 +01:00
parent 8370ddf066
commit 5a594b2b7f
2 changed files with 130 additions and 3 deletions
+103
View File
@@ -346,6 +346,109 @@ async fn delete_owned_session_kills_it() {
assert!(!h.manager.has(session.id).await);
}
/// Spawn a session through the manager and return its id.
async fn spawn_via_manager(h: &Harness) -> Uuid {
h.manager
.create(CreateSessionRequest {
cwd: "/tmp".into(),
command: "sleep 60".into(),
env: HashMap::new(),
metadata: HashMap::new(),
worktree: None,
})
.await
.unwrap()
.id
}
/// The agent activity endpoint on the wire (#105): a report lands in the
/// session snapshot with its detail.
#[tokio::test]
async fn post_activity_reports_error_with_detail() {
let h = spawn("naps-laptop").await;
let id = spawn_via_manager(&h).await;
let resp = reqwest::Client::new()
.post(url(h.addr, &format!("/api/sessions/{id}/activity")))
.json(&serde_json::json!({
"state": "Error",
"detail": "cargo build failed"
}))
.send()
.await
.unwrap();
assert_eq!(resp.status(), reqwest::StatusCode::NO_CONTENT);
let sessions: Vec<Session> = reqwest::get(url(h.addr, "/api/sessions"))
.await
.unwrap()
.json()
.await
.unwrap();
let s = sessions.iter().find(|s| s.id == id).unwrap();
assert_eq!(s.activity, maestro_protocol::ActivityState::Error);
assert_eq!(s.activity_detail.as_deref(), Some("cargo build failed"));
}
#[tokio::test]
async fn post_activity_to_unknown_session_returns_404() {
let h = spawn("naps-laptop").await;
let resp = reqwest::Client::new()
.post(url(
h.addr,
&format!("/api/sessions/{}/activity", Uuid::new_v4()),
))
.json(&serde_json::json!({ "state": "Idle" }))
.send()
.await
.unwrap();
assert_eq!(resp.status(), reqwest::StatusCode::NOT_FOUND);
}
/// Unknown state values are rejected rather than coerced (#105).
#[tokio::test]
async fn post_activity_rejects_an_unknown_state_value() {
let h = spawn("naps-laptop").await;
let id = spawn_via_manager(&h).await;
let resp = reqwest::Client::new()
.post(url(h.addr, &format!("/api/sessions/{id}/activity")))
.json(&serde_json::json!({ "state": "Teleported" }))
.send()
.await
.unwrap();
assert!(resp.status().is_client_error(), "got {}", resp.status());
}
#[tokio::test]
async fn post_activity_caps_an_overlong_detail_server_side() {
let h = spawn("naps-laptop").await;
let id = spawn_via_manager(&h).await;
let resp = reqwest::Client::new()
.post(url(h.addr, &format!("/api/sessions/{id}/activity")))
.json(&serde_json::json!({
"state": "Error",
"detail": "x".repeat(10_000)
}))
.send()
.await
.unwrap();
assert_eq!(resp.status(), reqwest::StatusCode::NO_CONTENT);
let sessions: Vec<Session> = reqwest::get(url(h.addr, "/api/sessions"))
.await
.unwrap()
.json()
.await
.unwrap();
let s = sessions.iter().find(|s| s.id == id).unwrap();
assert_eq!(
s.activity_detail.as_ref().map(|d| d.chars().count()),
Some(maestro_daemon::MAX_REPORT_DETAIL_CHARS)
);
}
/// The kill guard on the wire (#79): `DELETE /api/sessions/:id` answers 409
/// with the structured breakdown, and the session keeps running.
#[tokio::test]