feat(lib): send and receive private messages

Wire private messaging through the client:

- MessageFactory::build_message_user (code 22) and build_message_acked
  (code 23);
- the incoming MessageUser handler now acknowledges freshly delivered
  messages and forwards them to the client instead of only logging them;
- ClientContext buffers received UserMessages;
- Client::send_private_message and Client::take_private_messages expose the
  feature, and UserMessage (with accessors) is re-exported from the crate.

Verified end to end against soulfind: a_private_message_is_delivered_between_users
logs in two clients and asserts one receives the other's message.
This commit is contained in:
michel
2026-07-19 13:31:30 +02:00
parent f61e02e05c
commit a7372cc88e
6 changed files with 195 additions and 3 deletions
+36
View File
@@ -251,6 +251,42 @@ fn a_search_is_forwarded_to_a_connected_peer() {
assert!(receiver.get_all_searches().contains_key(probe));
}
#[test]
fn a_private_message_is_delivered_between_users() {
let server = server_or_skip!();
// Two logged-in users, one messages the other through the server.
let mut alice =
Client::with_settings(server.settings("e2e_alice_pm", "pw"));
let mut bob = Client::with_settings(server.settings("e2e_bob_pm", "pw"));
alice.connect().expect("alice connect");
bob.connect().expect("bob connect");
assert!(alice.login().expect("alice login"));
assert!(bob.login().expect("bob login"));
let body = "hello bob, this is alice";
alice
.send_private_message("e2e_bob_pm", body)
.expect("send private message");
// Delivery is asynchronous; poll Bob's inbox until the message arrives.
let deadline = Instant::now() + Duration::from_secs(5);
let mut received = Vec::new();
while Instant::now() < deadline {
received.extend(bob.take_private_messages());
if !received.is_empty() {
break;
}
std::thread::sleep(Duration::from_millis(100));
}
let message = received
.iter()
.find(|m| m.message() == body)
.expect("bob should receive alice's message");
assert_eq!(message.username(), "e2e_alice_pm");
}
#[test]
fn login_succeeds_with_listener_enabled() {
let server = server_or_skip!();