fix(daemon): reject dead structured sessions
This commit is contained in:
@@ -556,9 +556,9 @@ async fn structured_ws(
|
||||
})?
|
||||
else {
|
||||
return Err(error_response(
|
||||
StatusCode::NOT_FOUND,
|
||||
"unknown_structured_session",
|
||||
"no live structured session with this id",
|
||||
StatusCode::CONFLICT,
|
||||
"not_structured",
|
||||
"session is not a live structured session",
|
||||
));
|
||||
};
|
||||
Ok(ws.on_upgrade(move |socket| async move {
|
||||
|
||||
@@ -103,6 +103,15 @@ pub struct SessionRecord {
|
||||
acp_event_pump: Option<watch::Receiver<bool>>,
|
||||
}
|
||||
|
||||
impl SessionRecord {
|
||||
fn is_live_structured(&self) -> bool {
|
||||
self.session.view == SessionView::Structured
|
||||
&& self.session.status == SessionStatus::Running
|
||||
&& self.acp.is_some()
|
||||
&& self.acp_events.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AcpEventStream {
|
||||
log: EventLog,
|
||||
@@ -341,6 +350,9 @@ impl SessionManager {
|
||||
let Some(record) = sessions.get(&id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
if !record.is_live_structured() {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(stream) = &record.acp_events else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -378,7 +390,7 @@ impl SessionManager {
|
||||
let stream = {
|
||||
let sessions = self.inner.sessions.lock().await;
|
||||
let record = sessions.get(&id).ok_or(SessionError::NotFound(id))?;
|
||||
if record.session.view != SessionView::Structured {
|
||||
if !record.is_live_structured() {
|
||||
return Ok(None);
|
||||
}
|
||||
record.acp_events.clone()
|
||||
@@ -441,9 +453,10 @@ impl SessionManager {
|
||||
> {
|
||||
let stream = {
|
||||
let sessions = self.inner.sessions.lock().await;
|
||||
let Some(record) = sessions.get(&id) else {
|
||||
let record = sessions.get(&id).ok_or(SessionError::NotFound(id))?;
|
||||
if !record.is_live_structured() {
|
||||
return Ok(None);
|
||||
};
|
||||
}
|
||||
let Some(stream) = &record.acp_events else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -488,9 +501,7 @@ impl SessionManager {
|
||||
let (acp, stream) = {
|
||||
let sessions = self.inner.sessions.lock().await;
|
||||
let record = sessions.get(&id).ok_or(SessionError::NotFound(id))?;
|
||||
if record.session.view != SessionView::Structured
|
||||
|| record.session.status != SessionStatus::Running
|
||||
{
|
||||
if !record.is_live_structured() {
|
||||
return Ok(BeginAcpTurn::Unavailable);
|
||||
}
|
||||
(record.acp.clone(), record.acp_events.clone())
|
||||
@@ -512,6 +523,9 @@ impl SessionManager {
|
||||
let (acp, stream) = {
|
||||
let sessions = self.inner.sessions.lock().await;
|
||||
let record = sessions.get(&id).ok_or(SessionError::NotFound(id))?;
|
||||
if !record.is_live_structured() {
|
||||
return Ok(false);
|
||||
}
|
||||
(record.acp.clone(), record.acp_events.clone())
|
||||
};
|
||||
let (Some(acp), Some(stream)) = (acp, stream) else {
|
||||
|
||||
@@ -445,7 +445,7 @@ async fn structured_replay_pages_projected_events_in_cursor_order() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn structured_replay_rejects_unknown_and_terminal_sessions() {
|
||||
async fn structured_replay_rejects_unknown_terminal_and_dead_sessions() {
|
||||
let h = spawn("naps-laptop").await;
|
||||
let client = reqwest::Client::new();
|
||||
let unknown = client
|
||||
@@ -478,6 +478,40 @@ async fn structured_replay_rejects_unknown_and_terminal_sessions() {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), reqwest::StatusCode::CONFLICT);
|
||||
|
||||
let fake = FakeAcp::new();
|
||||
let dead = structured_session(&h, &fake).await;
|
||||
h.manager.shutdown().await;
|
||||
assert_eq!(h.manager.status(dead).await, Some(SessionStatus::Dead));
|
||||
let response = client
|
||||
.get(url(
|
||||
h.addr,
|
||||
&format!("/api/sessions/{dead}/structured/replay"),
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), reqwest::StatusCode::CONFLICT);
|
||||
let error: ApiError = response.json().await.unwrap();
|
||||
assert_eq!(error.error.code, "not_structured");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn structured_ws_rejects_a_dead_session() {
|
||||
let h = spawn("naps-laptop").await;
|
||||
let fake = FakeAcp::new();
|
||||
let id = structured_session(&h, &fake).await;
|
||||
h.manager.shutdown().await;
|
||||
assert_eq!(h.manager.status(id).await, Some(SessionStatus::Dead));
|
||||
|
||||
let err = connect_async(ws_url(h.addr, &format!("/api/structured/{id}")))
|
||||
.await
|
||||
.expect_err("dead structured session must not upgrade");
|
||||
let message = err.to_string();
|
||||
assert!(
|
||||
message.contains("409") || message.to_lowercase().contains("conflict"),
|
||||
"{message}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user