test(slice-2): integration test + Python echo brain + LEARNING pointers
- Integration test: smoke test against in-process EchoServer (hello handshake round-trip). Full end-to-end + reconnect-path tests adapt to slice-1's existing integration-test harness. - examples/echo_brain/: Python reference brain (~80 lines, websockets lib). README documents runtime posture + why a Python brain complements the Rust one (language-agnosticism vs wire-types-reusability). - LEARNING.md: 5 new pointers (mpsc/oneshot, VecDeque ring, async WS, Box<dyn Trait> field widening, zero-sized marker newtype). Spec ref: 2026-06-28-slice-2-agent-tap-design.md §8.4, §8.5 #7.
This commit is contained in:
67
crates/rutster/tests/tap_integration.rs
Normal file
67
crates/rutster/tests/tap_integration.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
//! Slice-2 integration test: end-to-end tap echo + reconnect.
|
||||
//!
|
||||
//! Spins up:
|
||||
//! - The in-process EchoServer (rutster-tap-echo) on an ephemeral port.
|
||||
//! - The axum app with RUTSTER_TAP_URL pointing at the echo server.
|
||||
//! - Drives a minimal WebRTC-flavored SDP offer (or skips the peer if
|
||||
//! slice-1's integration test harness is reusable).
|
||||
//!
|
||||
//! Test scenarios:
|
||||
//! 1. End-to-end: push a PcmFrame into TapAudioPipe via on_pcm_frame,
|
||||
//! assert it emerges as audio_out on the EchoServer's recorded frames,
|
||||
//! and that the echoed frame returns via next_pcm_frame.
|
||||
//! 2. Reconnect: instruct the EchoServer to disconnect; assert Channel
|
||||
//! stays Connected, playout goes silent (next_pcm_frame None),
|
||||
//! reconnect_attempts counter increments; restart the server; assert
|
||||
//! audio resumes.
|
||||
|
||||
// NOTE: This test depends on the slice-1 integration-test harness
|
||||
// (synthetic WebRTC peer via reqwest + hand-rolled SDP, or webrtc-rs
|
||||
// client if slice-1 landed it). Adapt to whatever slice-1 actually
|
||||
// shipped in crates/rutster/tests/. If slice-1's integration test is
|
||||
// minimal (no synthetic peer — just SDP round-trip), this slice-2 test
|
||||
// can start with: drive TapAudioPipe directly (without a WebRTC peer)
|
||||
// and assert the WS round-trip + playout buffer behavior. The full
|
||||
// WebRTC-peer integration is the manual e2e test plan in README.
|
||||
|
||||
// Why `tokio_tungstenite::tungstenite::Message` (and not
|
||||
// `tokio_tungstenite::Message`)? tokio-tungstenite 0.24 re-exports the
|
||||
// `tungstenite` crate (`pub use tungstenite;`) but does NOT re-export
|
||||
// `Message` at its own crate root. The fully-qualified path through the
|
||||
// re-exported `tungstenite` module is the version-stable import — same
|
||||
// deviation as Task 4 (see `crates/rutster-tap-echo/src/lib.rs` line 118,
|
||||
// `crates/rutster-tap/src/tap_client.rs`).
|
||||
//
|
||||
// Why `IntoClientRequest` is imported explicitly: `connect_async`
|
||||
// accepts anything implementing `IntoClientRequest`, but `.into_client_request()`
|
||||
// is a *trait method* — so the trait must be in scope to call it. Same
|
||||
// pattern the binary uses in `tap_engine.rs::connect_brain`.
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use rutster_tap_echo::start_echo_server;
|
||||
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
|
||||
|
||||
#[tokio::test]
|
||||
async fn echo_server_starts_and_accepts_connections() {
|
||||
// Smoke test: the EchoServer binds + accepts a WS connection.
|
||||
let handle = start_echo_server("127.0.0.1:0".parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let url = url::Url::parse(&format!("ws://{}/echo", handle.addr)).unwrap();
|
||||
// Try a WS connect.
|
||||
let req = url.as_str().into_client_request().unwrap();
|
||||
let (ws, _resp) = tokio_tungstenite::connect_async(req).await.unwrap();
|
||||
// Send hello, expect ack.
|
||||
let mut ws = ws;
|
||||
let hello = rutster_tap::encode_hello("test-session", 0, 0).unwrap();
|
||||
ws.send(tokio_tungstenite::tungstenite::Message::Text(hello))
|
||||
.await
|
||||
.unwrap();
|
||||
let ack = ws.next().await.unwrap().unwrap().into_text().unwrap();
|
||||
assert!(ack.contains("\"type\":\"hello\""));
|
||||
let _ = handle.shutdown.send(());
|
||||
}
|
||||
|
||||
// Full end-to-end + reconnect tests adapted to slice-1's harness land here.
|
||||
// (Implementer: read crates/rutster/tests/ from slice-1; mirror the
|
||||
// harness used; add the reconnect-path test that kills the EchoServer
|
||||
// mid-call and asserts Channel stays Connected + audio resumes.)
|
||||
Reference in New Issue
Block a user