matrix-rust-sdk/crates/matrix-sdk-ui/tests/integration/timeline/echo.rs

212 lines
8.3 KiB
Rust

// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{sync::Arc, time::Duration};
use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use futures_util::StreamExt;
use matrix_sdk::{config::SyncSettings, executor::spawn, ruma::MilliSecondsSinceUnixEpoch};
use matrix_sdk_test::{async_test, EventBuilder, JoinedRoomBuilder, TimelineTestEvent};
use matrix_sdk_ui::timeline::{
EventSendState, RoomExt, TimelineItem, TimelineItemContent, VirtualTimelineItem,
};
use ruma::{
event_id,
events::room::message::{MessageType, RoomMessageEventContent},
room_id, uint, TransactionId,
};
use serde_json::json;
use stream_assert::assert_next_matches;
use wiremock::{
matchers::{header, method, path_regex},
Mock, ResponseTemplate,
};
use crate::{logged_in_client, mock_encryption_state, mock_sync};
#[async_test]
async fn echo() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
let mut ev_builder = EventBuilder::new();
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
server.reset().await;
let room = client.get_room(room_id).unwrap();
let timeline = Arc::new(room.timeline().await);
let (_, mut timeline_stream) = timeline.subscribe().await;
let event_id = event_id!("$wWgymRfo7ri1uQx0NXO40vLJ");
let txn_id: &TransactionId = "my-txn-id".into();
mock_encryption_state(&server, false).await;
Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*"))
.and(header("authorization", "Bearer 1234"))
.respond_with(ResponseTemplate::new(200).set_body_json(&json!({ "event_id": event_id })))
.mount(&server)
.await;
// Don't move the original timeline, it must live until the end of the test
let timeline = timeline.clone();
#[allow(unknown_lints, clippy::redundant_async_block)] // false positive
let send_hdl = spawn(async move {
timeline
.send(RoomMessageEventContent::text_plain("Hello, World!").into(), Some(txn_id))
.await
});
let _day_divider = assert_matches!(timeline_stream.next().await, Some(VectorDiff::PushBack { value }) => value);
let local_echo = assert_matches!(timeline_stream.next().await, Some(VectorDiff::PushBack { value }) => value);
let item = local_echo.as_event().unwrap();
assert_matches!(item.send_state(), Some(EventSendState::NotSentYet));
let msg = assert_matches!(item.content(), TimelineItemContent::Message(msg) => msg);
let text = assert_matches!(msg.msgtype(), MessageType::Text(text) => text);
assert_eq!(text.body, "Hello, World!");
// Wait for the sending to finish and assert everything was successful
send_hdl.await.unwrap();
let sent_confirmation = assert_matches!(
timeline_stream.next().await,
Some(VectorDiff::Set { index: 1, value }) => value
);
let item = sent_confirmation.as_event().unwrap();
assert_matches!(item.send_state(), Some(EventSendState::Sent { .. }));
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
TimelineTestEvent::Custom(json!({
"content": {
"body": "Hello, World!",
"msgtype": "m.text",
},
"event_id": "$7at8sd:localhost",
"origin_server_ts": 152038280,
"sender": "@example:localhost",
"type": "m.room.message",
"unsigned": { "transaction_id": txn_id, },
})),
));
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
server.reset().await;
// Local echo is removed
assert_matches!(timeline_stream.next().await, Some(VectorDiff::Remove { index: 1 }));
// Local echo day divider is removed
assert_matches!(timeline_stream.next().await, Some(VectorDiff::Remove { index: 0 }));
// New day divider is added
let new_item = assert_matches!(
timeline_stream.next().await,
Some(VectorDiff::PushBack { value }) => value
);
assert_matches!(&*new_item, TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_)));
// Remote echo is added
let remote_echo = assert_matches!(
timeline_stream.next().await,
Some(VectorDiff::PushBack { value }) => value
);
let item = remote_echo.as_event().unwrap();
assert!(item.is_own());
assert_eq!(item.timestamp(), MilliSecondsSinceUnixEpoch(uint!(152038280)));
}
#[async_test]
async fn dedup_by_event_id_late() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
let mut ev_builder = EventBuilder::new();
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
server.reset().await;
let room = client.get_room(room_id).unwrap();
let timeline = Arc::new(room.timeline().await);
let (_, mut timeline_stream) = timeline.subscribe().await;
let event_id = event_id!("$wWgymRfo7ri1uQx0NXO40vLJ");
let txn_id: &TransactionId = "my-txn-id".into();
mock_encryption_state(&server, false).await;
Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*"))
.and(header("authorization", "Bearer 1234"))
.respond_with(
ResponseTemplate::new(200)
.set_body_json(&json!({ "event_id": event_id }))
// Not great to use a timer for this, but it's what wiremock gives us right now.
// Ideally we'd wait on a channel to produce a value or sth. like that.
.set_delay(Duration::from_millis(100)),
)
.mount(&server)
.await;
let send_hdl = spawn(async move {
timeline
.send(RoomMessageEventContent::text_plain("Hello, World!").into(), Some(txn_id))
.await
});
assert_matches!(timeline_stream.next().await, Some(VectorDiff::PushBack { .. })); // day divider
let local_echo = assert_matches!(timeline_stream.next().await, Some(VectorDiff::PushBack { value }) => value);
let item = local_echo.as_event().unwrap();
assert_matches!(item.send_state(), Some(EventSendState::NotSentYet));
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
TimelineTestEvent::Custom(json!({
"content": {
"body": "Hello, World!",
"msgtype": "m.text",
},
"event_id": event_id,
"origin_server_ts": 123456,
"sender": "@example:localhost",
"type": "m.room.message",
// no transaction ID
})),
));
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
assert_next_matches!(timeline_stream, VectorDiff::PushBack { .. }); // day divider
let remote_echo =
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => value);
let item = remote_echo.as_event().unwrap();
assert_eq!(item.event_id(), Some(event_id));
// Wait for the sending to finish
send_hdl.await.unwrap();
// Local echo and its day divider are removed.
assert_next_matches!(timeline_stream, VectorDiff::Remove { index: 1 });
assert_next_matches!(timeline_stream, VectorDiff::Remove { index: 0 });
}