commit 59d8c284e6b41437a03c14366b925212eb448e5b
parent a45d9b443f1ff42f99a846c04678b909e46d8850
Author: triesap <tyson@radroots.org>
Date: Sun, 14 Jun 2026 17:30:13 -0700
runtime: prove malformed chorus parity
Diffstat:
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)
}