From 30698aa7cedb4600fe4cbabe9618c95a93880c71 Mon Sep 17 00:00:00 2001 From: Michel de Graaf Date: Wed, 29 Jul 2026 10:37:48 +0200 Subject: [PATCH] test(daemon): match a reply by its id instead of taking the next line A connection that has joined a room receives event notifications on the same socket as its replies, so a notification landing between a request and its answer was read as the answer. Only replies carry an id. --- .gitattributes | 4 ++++ soulseek-rs/tests/daemon_protocol_e2e.rs | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdd3961 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Generated and compared byte-for-byte by `daemon::openrpc::document_is_current`, +# which builds its expectation with `\n`. A CRLF checkout would fail that test on +# Windows. +docs/openrpc.json text eol=lf diff --git a/soulseek-rs/tests/daemon_protocol_e2e.rs b/soulseek-rs/tests/daemon_protocol_e2e.rs index cd1fbc4..917f9a1 100644 --- a/soulseek-rs/tests/daemon_protocol_e2e.rs +++ b/soulseek-rs/tests/daemon_protocol_e2e.rs @@ -158,9 +158,21 @@ impl Connection { } /// Send one line and read the reply, `None` if the daemon hung up first. + /// + /// Events share the socket with replies, so a subscribed connection can + /// have a notification land between the request and its answer. Only a + /// reply carries an `id`; skip everything that does not. fn call(&mut self, line: &str) -> Option { self.send(line)?; - self.read() + loop { + let reply = self.read()?; + let is_notification = + serde_json::from_str::(&reply) + .is_ok_and(|parsed| parsed.get("id").is_none()); + if !is_notification { + return Some(reply); + } + } } fn send(&mut self, line: &str) -> Option<()> {