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-tui/tests/session_task_ws.rs
naps62-yolo 888a00d177
ci / rust (push) Failing after 5m11s
feat(maestro-tui): SessionTask + SessionEmulators + ResizeTracker (NAP-279)
Per-attached-session task:
- Owns one WS to `WS /api/attach/:id` and a `vt100::Parser`.
- Binary frames → `parser.process(bytes)`. Text frames → `ClientControl::Ping`
  is replied to as Pong; other text frames are ignored in v1.
- `SessionInput::Bytes(b)` becomes a binary frame; `Resize { cols, rows }`
  resizes the parser AND sends `ClientControl::Resize` on the wire.
- WS drop → `Reconnecting` with exponential backoff (1s → 30s, ±25% jitter).
  Parser screen is **not** reset across reconnects, so the user keeps seeing
  the last-known frame instead of a blank.
- `SessionHandle::end()` transitions the task to `Ended` and stops it.

SessionEmulators:
- `(SessionId → SessionHandle)` registry.
- Owns a `ResizeTracker` whose `set_pane_size(sid, pane_id, (cols, rows))`
  and `session_size(sid) -> Option<(cols, rows)>` compute the per-dimension
  minimum across all panes bound to the same session — the value the
  dispatcher feeds to the daemon.

Tests:
- 7 unit tests for `ResizeTracker` covering per-dim min, cross-session
  isolation, and pane/session drop semantics.
- 2 unit tests for `SessionState` transitions.
- 1 axum-WS round-trip integration test asserting `"hello"` lands in the
  vt100 screen within 500ms.
2026-05-28 22:15:49 +00:00

58 lines
1.9 KiB
Rust

//! Acceptance test for `SessionTask`: a fake WS server sends `hello\r\n` as a
//! binary frame, and we assert the `vt100` screen sees it within the
//! interval.
use std::time::Duration;
use axum::extract::ws::{Message, WebSocketUpgrade};
use axum::response::Response;
use axum::routing::any;
use axum::Router;
use maestro_tui::session_task::{SessionState, SessionTask};
async fn attach_handler(ws: WebSocketUpgrade) -> Response {
ws.on_upgrade(|mut socket| async move {
// Push initial bytes immediately.
if socket
.send(Message::Binary(b"hello\r\n".to_vec()))
.await
.is_err()
{
return;
}
// Keep the connection open so the client doesn't enter reconnect.
tokio::time::sleep(Duration::from_secs(2)).await;
})
}
#[tokio::test(flavor = "multi_thread")]
async fn vt100_screen_receives_pty_bytes() {
let app = Router::new().route("/api/attach/x", any(attach_handler));
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
let url = format!("ws://{addr}/api/attach/x");
let handle = SessionTask::spawn(url, 24, 80);
// Poll for up to 500 ms looking for "hello" in row 0.
for _ in 0..50 {
tokio::time::sleep(Duration::from_millis(10)).await;
if handle.state() != SessionState::Live {
continue;
}
let parser = handle.screen.lock().expect("vt100 parser mutex");
let screen = parser.screen();
let row: String = (0..5)
.map(|c| screen.cell(0, c).unwrap().contents())
.collect();
if row.starts_with("hello") {
return;
}
}
panic!(
"vt100 screen never contained 'hello'; state={:?}",
handle.state()
);
}