feat(protocol): interests, tickers, global room and cant-connect
Adds the server messages the client had no way to speak: user and room search (42/120), room tickers (113-116), the global room feed (150-152), interests and the recommendation queries built on them (51/52/54/56/57/ 110/111/112/117/118), a keepalive ping (32), gifted privileges (123), a password change (142) and multi-user private messages (149). CantConnectToPeer (1001) closes a real gap in both directions: a dial the server asked us to make now answers the waiting peer instead of falling back to brokering the same unreachable link the other way, and an incoming one ends the wait on downloads queued for that peer rather than letting them sit until a timeout.
This commit is contained in:
@@ -838,3 +838,116 @@ fn a_peer_message_nobody_could_deliver_expires() {
|
||||
ctx.expire_pending_peer_messages(Instant::now() + PENDING_PEER_TTL);
|
||||
assert!(ctx.take_peer_messages("ghost").is_empty(), "expired");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_ticker_replaces_that_users_previous_one() {
|
||||
// The server treats a user's ticker as singular: a second one from the
|
||||
// same user supersedes the first rather than stacking beside it.
|
||||
let mut ctx = ClientContext::new();
|
||||
ctx.apply_room_event(RoomEvent::Tickers {
|
||||
room: "jazz".into(),
|
||||
tickers: vec![
|
||||
RoomTicker {
|
||||
username: "alice".into(),
|
||||
ticker: "first".into(),
|
||||
},
|
||||
RoomTicker {
|
||||
username: "bob".into(),
|
||||
ticker: "bobs".into(),
|
||||
},
|
||||
],
|
||||
});
|
||||
ctx.apply_room_event(RoomEvent::TickerAdded {
|
||||
room: "jazz".into(),
|
||||
username: "alice".into(),
|
||||
ticker: "second".into(),
|
||||
});
|
||||
|
||||
let board = ctx.room_tickers("jazz");
|
||||
assert_eq!(board.len(), 2);
|
||||
assert_eq!(
|
||||
board.iter().find(|t| t.username == "alice").unwrap().ticker,
|
||||
"second"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_removed_ticker_leaves_the_rest_of_the_board() {
|
||||
let mut ctx = ClientContext::new();
|
||||
ctx.apply_room_event(RoomEvent::Tickers {
|
||||
room: "jazz".into(),
|
||||
tickers: vec![
|
||||
RoomTicker {
|
||||
username: "alice".into(),
|
||||
ticker: "a".into(),
|
||||
},
|
||||
RoomTicker {
|
||||
username: "bob".into(),
|
||||
ticker: "b".into(),
|
||||
},
|
||||
],
|
||||
});
|
||||
ctx.apply_room_event(RoomEvent::TickerRemoved {
|
||||
room: "jazz".into(),
|
||||
username: "alice".into(),
|
||||
});
|
||||
|
||||
let board = ctx.room_tickers("jazz");
|
||||
assert_eq!(board.len(), 1);
|
||||
assert_eq!(board[0].username, "bob");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_global_message_is_queued_without_touching_room_membership() {
|
||||
// The global feed carries messages from rooms we have not joined; they
|
||||
// must not invent a roster for those rooms.
|
||||
let mut ctx = ClientContext::new();
|
||||
ctx.apply_room_event(RoomEvent::GlobalMessage {
|
||||
room: "elsewhere".into(),
|
||||
username: "alice".into(),
|
||||
message: "hi".into(),
|
||||
});
|
||||
assert!(ctx.room_members("elsewhere").is_empty());
|
||||
assert_eq!(ctx.take_room_events().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn own_and_global_recommendations_are_kept_apart() {
|
||||
let mut ctx = ClientContext::new();
|
||||
ctx.apply_recommendations(
|
||||
false,
|
||||
vec![Recommendation {
|
||||
item: "jazz".into(),
|
||||
rating: 2,
|
||||
}],
|
||||
Vec::new(),
|
||||
);
|
||||
ctx.apply_recommendations(
|
||||
true,
|
||||
vec![Recommendation {
|
||||
item: "pop".into(),
|
||||
rating: 9,
|
||||
}],
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
assert_eq!(ctx.recommendations(false).unwrap().0[0].item, "jazz");
|
||||
assert_eq!(ctx.recommendations(true).unwrap().0[0].item, "pop");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asking_again_about_a_users_interests_drops_the_previous_answer() {
|
||||
let mut ctx = ClientContext::new();
|
||||
ctx.apply_user_interests(UserInterests {
|
||||
username: "alice".into(),
|
||||
likes: vec!["jazz".into()],
|
||||
hates: Vec::new(),
|
||||
});
|
||||
assert!(ctx.user_interests("alice").is_some());
|
||||
|
||||
ctx.invalidate_user_interests("alice");
|
||||
assert!(
|
||||
ctx.user_interests("alice").is_none(),
|
||||
"a stale answer must not be mistaken for the next one"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user