diff --git a/crates/matrix-sdk-ui/src/room_list_service/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/mod.rs index f6045837f9..143b8b77b9 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/mod.rs @@ -135,6 +135,9 @@ impl RoomListService { .with_to_device_extension( assign!(http::request::ToDevice::default(), { enabled: Some(true) }), ); + } else { + // TODO: This is racy with encryption, needs cross-process lock + builder = builder.share_pos(); } let sliding_sync = builder diff --git a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs index b37ec389f9..4fdf66f28e 100644 --- a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs @@ -6,7 +6,11 @@ use std::{ use assert_matches::assert_matches; use eyeball_im::VectorDiff; use futures_util::{pin_mut, FutureExt, StreamExt}; -use matrix_sdk::{test_utils::logged_in_client_with_server, Client}; +use matrix_sdk::{ + config::RequestConfig, + test_utils::{logged_in_client_with_server, set_client_session, test_client_builder}, + Client, +}; use matrix_sdk_base::{ sliding_sync::http::request::RoomSubscription, sync::UnreadNotificationsCount, }; @@ -27,6 +31,7 @@ use ruma::{ }; use serde_json::json; use stream_assert::{assert_next_matches, assert_pending}; +use tempfile::TempDir; use tokio::{spawn, sync::mpsc::channel, task::yield_now}; use wiremock::{ matchers::{header, method, path}, @@ -42,12 +47,30 @@ async fn new_room_list_service() -> Result<(Client, MockServer, RoomListService) Ok((client, server, room_list)) } +async fn new_persistent_room_list_service( + store_path: &std::path::Path, +) -> Result<(MockServer, RoomListService), Error> { + let server = MockServer::start().await; + let client = test_client_builder(Some(server.uri().to_string())) + .request_config(RequestConfig::new().disable_retry()) + .sqlite_store(store_path, None) + .build() + .await + .unwrap(); + set_client_session(&client).await; + + let room_list = RoomListService::new(client.clone()).await?; + + Ok((server, room_list)) +} + // Same macro as in the main, with additional checking that the state // before/after the sync loop match those we expect. macro_rules! sync_then_assert_request_and_fake_response { ( [$server:ident, $room_list:ident, $stream:ident] $( states = $pre_state:pat => $post_state:pat, )? + $( assert pos $pos:expr, )? assert request $assert_request:tt { $( $request_json:tt )* }, respond with = $( ( code $code:expr ) )? { $( $response_json:tt )* } $( , after delay = $response_delay:expr )? @@ -57,6 +80,7 @@ macro_rules! sync_then_assert_request_and_fake_response { [$server, $room_list, $stream] sync matches Some(Ok(_)), $( states = $pre_state => $post_state, )? + $( assert pos $pos, )? assert request $assert_request { $( $request_json )* }, respond with = $( ( code $code ) )? { $( $response_json )* }, $( after delay = $response_delay, )? @@ -67,6 +91,7 @@ macro_rules! sync_then_assert_request_and_fake_response { [$server:ident, $room_list:ident, $stream:ident] sync matches $sync_result:pat, $( states = $pre_state:pat => $post_state:pat, )? + $( assert pos $pos:expr, )? assert request $assert_request:tt { $( $request_json:tt )* }, respond with = $( ( code $code:expr ) )? { $( $response_json:tt )* } $( , after delay = $response_delay:expr )? @@ -84,6 +109,7 @@ macro_rules! sync_then_assert_request_and_fake_response { let next = super::sliding_sync_then_assert_request_and_fake_response! { [$server, $stream] sync matches $sync_result, + $( assert pos $pos, )? assert request $assert_request { $( $request_json )* }, respond with = $( ( code $code ) )? { $( $response_json )* }, $( after delay = $response_delay, )? @@ -479,6 +505,7 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] states = Init => SettingUp, + assert pos None::, assert request >= { "lists": { ALL_ROOMS: { @@ -506,6 +533,7 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] states = SettingUp => Running, + assert pos Some("0"), assert request >= { "lists": { ALL_ROOMS: { @@ -533,6 +561,7 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] states = Running => Running, + assert pos Some("1"), assert request >= { "lists": { ALL_ROOMS: { @@ -555,6 +584,70 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> { Ok(()) } +#[async_test] +async fn test_sync_resumes_from_previous_state_after_restart() -> Result<(), Error> { + let tmp_dir = TempDir::new().unwrap(); + let store_path = tmp_dir.path(); + + { + let (server, room_list) = new_persistent_room_list_service(&store_path).await?; + let sync = room_list.sync(); + pin_mut!(sync); + + sync_then_assert_request_and_fake_response! { + [server, room_list, sync] + states = Init => SettingUp, + assert pos None::, + assert request >= { + "lists": { + ALL_ROOMS: { + "ranges": [[0, 19]], + }, + }, + }, + respond with = { + "pos": "0", + "lists": { + ALL_ROOMS: { + "count": 10, + }, + }, + "rooms": {}, + }, + }; + } + + { + let (server, room_list) = new_persistent_room_list_service(&store_path).await?; + let sync = room_list.sync(); + pin_mut!(sync); + + sync_then_assert_request_and_fake_response! { + [server, room_list, sync] + states = Init => SettingUp, + assert pos Some("0"), + assert request >= { + "lists": { + ALL_ROOMS: { + "ranges": [[0, 19]], + }, + }, + }, + respond with = { + "pos": "1", + "lists": { + ALL_ROOMS: { + "count": 10, + }, + }, + "rooms": {}, + }, + }; + } + + Ok(()) +} + #[async_test] async fn test_sync_resumes_from_error() -> Result<(), Error> { let (_, server, room_list) = new_room_list_service().await?; diff --git a/crates/matrix-sdk-ui/tests/integration/sliding_sync.rs b/crates/matrix-sdk-ui/tests/integration/sliding_sync.rs index b267fa96bb..5ec1dda2c0 100644 --- a/crates/matrix-sdk-ui/tests/integration/sliding_sync.rs +++ b/crates/matrix-sdk-ui/tests/integration/sliding_sync.rs @@ -58,6 +58,7 @@ impl Match for SlidingSyncMatcher { macro_rules! sliding_sync_then_assert_request_and_fake_response { ( [$server:ident, $stream:ident] + $( assert pos $pos:expr, )? assert request $sign:tt { $( $request_json:tt )* }, respond with = $( ( code $code:expr ) )? { $( $response_json:tt )* } $( , after delay = $response_delay:expr )? @@ -66,6 +67,7 @@ macro_rules! sliding_sync_then_assert_request_and_fake_response { sliding_sync_then_assert_request_and_fake_response! { [$server, $stream] sync matches Some(Ok(_)), + $( assert pos $pos, )? assert request $sign { $( $request_json )* }, respond with = $( ( code $code ) )? { $( $response_json )* }, $( after delay = $response_delay, )? @@ -75,6 +77,7 @@ macro_rules! sliding_sync_then_assert_request_and_fake_response { ( [$server:ident, $stream:ident] sync matches $sync_result:pat, + $( assert pos $pos:expr, )? assert request $sign:tt { $( $request_json:tt )* }, respond with = $( ( code $code:expr ) )? { $( $response_json:tt )* } $( , after delay = $response_delay:expr )? @@ -117,6 +120,14 @@ macro_rules! sliding_sync_then_assert_request_and_fake_response { root.remove("txn_id"); } + // Validate `pos` from the query parameter if specified. + $( + match $pos { + Some(pos) => assert!(wiremock::matchers::query_param("pos", pos).matches(request)), + None => assert!(wiremock::matchers::query_param_is_missing("pos").matches(request)), + } + )? + if let Err(error) = assert_json_diff::assert_json_matches_no_panic( &json_value, &json!({ $( $request_json )* }),