//! Tests de integración: levanta server + client en el mismo proceso, //! ejercita el round-trip completo del protocolo. use std::collections::BTreeSet; use std::time::Duration; use brahman_card::{ Card, CgroupSpec, NamespaceSet, Payload, ResourceLimits, SomaSpec, Supervision, CARD_SCHEMA_VERSION, }; use brahman_handshake::{ client::{Client, ClientError}, codec::{read_frame, write_frame}, messages::{Frame, HandshakeError, Hello, Ping}, server::{Server, ServerConfig}, }; use tokio::net::UnixStream; use ulid::Ulid; fn sample_card(label: &str) -> Card { Card { schema_version: CARD_SCHEMA_VERSION, id: Ulid::new(), lineage: None, label: label.into(), provides: BTreeSet::new(), requires: BTreeSet::new(), soma: SomaSpec { cgroup: CgroupSpec { path: "ente.slice/test".into(), cpu_weight: None, io_weight: None, }, namespaces: NamespaceSet::default(), rlimits: ResourceLimits::default(), cpu_affinity: None, }, payload: Payload::Virtual, supervision: Supervision::OneShot, ..Default::default() } } /// Genera una ruta de socket única bajo TMPDIR. No la creamos — /// el server la creará al hacer bind. fn sock_path(name: &str) -> std::path::PathBuf { std::env::temp_dir().join(format!( "brahman-test-{}-{}-{}.sock", name, std::process::id(), Ulid::new() )) } #[tokio::test] async fn full_handshake_roundtrip() { let path = sock_path("happy"); let server = Server::bind(&path, ServerConfig { init_attached: true }).unwrap(); let session_handle = tokio::spawn({ async move { let session = server.accept_one().await.unwrap(); session.handle().await.unwrap(); } }); let mut client = Client::connect(&path, sample_card("alpha")).await.unwrap(); assert!(client.server_info().init_attached); assert_eq!( client.server_info().protocol_version, brahman_card::PROTOCOL_VERSION ); let mut last = 0u64; for _ in 0..3 { let ts = client.ping().await.unwrap(); assert!(ts >= last); last = ts; tokio::time::sleep(Duration::from_millis(2)).await; } client.farewell().await.unwrap(); tokio::time::timeout(Duration::from_secs(2), session_handle) .await .expect("server hung after farewell") .unwrap(); } #[tokio::test] async fn rejects_invalid_card_client_side() { let path = sock_path("invalid"); let server = Server::bind(&path, ServerConfig::default()).unwrap(); let session_handle = tokio::spawn(async move { // No esperamos que el server complete: el cliente corta antes. let _ = tokio::time::timeout(Duration::from_secs(1), async move { let session = server.accept_one().await.unwrap(); session.handle().await.unwrap(); }) .await; }); let mut bad = sample_card("placeholder"); bad.label = String::new(); let err = Client::connect(&path, bad).await.unwrap_err(); assert!(matches!(err, ClientError::InvalidCard(_))); session_handle.abort(); } #[tokio::test] async fn server_rejects_protocol_mismatch() { let path = sock_path("mismatch"); let server = Server::bind(&path, ServerConfig::default()).unwrap(); let session_handle = tokio::spawn(async move { let session = server.accept_one().await.unwrap(); session.handle().await.unwrap(); }); let mut stream = UnixStream::connect(&path).await.unwrap(); let hello = Hello { schema_version: CARD_SCHEMA_VERSION, protocol_version: "999.0.0".into(), card: sample_card("future-module"), }; write_frame(&mut stream, &Frame::Hello(hello)).await.unwrap(); match read_frame(&mut stream).await.unwrap() { Frame::Error(HandshakeError::ProtocolMismatch(_)) => {} other => panic!("esperado ProtocolMismatch, got {other:?}"), } tokio::time::timeout(Duration::from_secs(2), session_handle) .await .expect("server hung after rejecting") .unwrap(); } #[tokio::test] async fn ping_before_hello_rejected() { let path = sock_path("ping-no-hello"); let server = Server::bind(&path, ServerConfig::default()).unwrap(); let session_handle = tokio::spawn(async move { let session = server.accept_one().await.unwrap(); session.handle().await.unwrap(); }); // Conectamos y mandamos un Ping sin haber saludado. let mut stream = UnixStream::connect(&path).await.unwrap(); write_frame( &mut stream, &Frame::Ping(Ping { session: Ulid::new(), }), ) .await .unwrap(); match read_frame(&mut stream).await.unwrap() { Frame::Error(HandshakeError::Rejected(_)) => {} other => panic!("esperado Rejected, got {other:?}"), } tokio::time::timeout(Duration::from_secs(2), session_handle) .await .expect("server hung after rejecting") .unwrap(); }