tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit 59d8c284e6b41437a03c14366b925212eb448e5b
parent a45d9b443f1ff42f99a846c04678b909e46d8850
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 17:30:13 -0700

runtime: prove malformed chorus parity

Diffstat:
Mcrates/tangle_runtime/src/session.rs | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 78 insertions(+), 1 deletion(-)

diff --git a/crates/tangle_runtime/src/session.rs b/crates/tangle_runtime/src/session.rs @@ -468,7 +468,10 @@ mod tests { use serde_json::json; use std::path::{Path, PathBuf}; use tangle_groups::StoreOffset; - use tangle_protocol::{ClientMessage, Filter, RelayMessage, SubscriptionId, filter_from_value}; + use tangle_protocol::{ + ClientMessage, Filter, RelayMessage, SubscriptionId, event_to_value, filter_from_value, + }; + use tangle_test_support::{FixtureKey, tangle_v2_event}; #[test] fn websocket_session_records_connection_time() { @@ -540,6 +543,72 @@ mod tests { } #[tokio::test] + async fn websocket_session_preserves_chorus_malformed_message_parity() { + let shutdown = TangleShutdownSignal::new(); + let (runtime, auth, events) = session_runtime("chorus-malformed-parity"); + let mut session = TangleWebSocketSession::new( + session_limits(16), + shutdown.subscribe(), + runtime, + auth, + events, + ) + .expect("session"); + let event = tangle_v2_event(FixtureKey::Member, 1_714_124_433, 1, Vec::new(), "parity") + .expect("event"); + for (raw, expected) in [ + ("{", None), + ( + "[\"NOTICE\",\"client\"]", + Some("[\"NOTICE\",\"invalid: client message command `NOTICE` is unsupported\"]"), + ), + ( + "[\"REQ\"]", + Some( + "[\"NOTICE\",\"invalid: REQ client message must contain a subscription id and filters\"]", + ), + ), + ( + "[\"CLOSE\",1]", + Some("[\"NOTICE\",\"invalid: CLOSE subscription id must be a string\"]"), + ), + ] { + assert_eq!( + session.dispatch_text(raw).await, + TangleSessionControl::Continue + ); + let text = take_outbound_text(&mut session); + if let Some(expected) = expected { + assert_eq!(text, expected); + } else { + assert!(text.starts_with("[\"NOTICE\",\"invalid: client message JSON is invalid:")); + } + } + + assert_eq!( + session + .dispatch_text("[\"REQ\",\"sub-search\",{\"search\":\"carrots\"}]") + .await, + TangleSessionControl::Continue + ); + assert_eq!( + take_outbound_text(&mut session), + "[\"CLOSED\",\"sub-search\",\"unsupported: search filters are not supported\"]" + ); + + assert_eq!( + session + .dispatch_text(&json!(["EVENT", event_to_value(&event)]).to_string()) + .await, + TangleSessionControl::Continue + ); + assert_eq!( + take_outbound_text(&mut session), + format!("[\"OK\",\"{}\",true,\"\"]", event.id().as_str()) + ); + } + + #[tokio::test] async fn websocket_session_scopes_subscriptions_per_connection() { let shutdown = TangleShutdownSignal::new(); let root = temp_root("connection-scope"); @@ -784,6 +853,14 @@ mod tests { } } + fn take_outbound_text(session: &mut TangleWebSocketSession) -> String { + let message = session.outbound_receiver.try_recv().expect("message"); + let Message::Text(text) = message else { + panic!("expected text message") + }; + text.to_string() + } + fn runtime_config(root: &Path) -> BaseRelayRuntimeConfig { runtime_config_with_outbound_queue(root, 8) }