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

158 lines
5.7 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::{io, sync::Arc};
use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use matrix_sdk::Error;
use matrix_sdk_test::async_test;
use ruma::{
event_id,
events::{room::message::RoomMessageEventContent, AnyMessageLikeEventContent},
};
use serde_json::json;
use stream_assert::assert_next_matches;
use super::{TestTimeline, ALICE, BOB};
use crate::timeline::event_item::EventSendState;
#[async_test]
async fn remote_echo_full_trip() {
let timeline = TestTimeline::new();
let mut stream = timeline.subscribe().await;
// Given a local event…
let txn_id = timeline
.handle_local_event(AnyMessageLikeEventContent::RoomMessage(
RoomMessageEventContent::text_plain("echo"),
))
.await;
let _day_divider = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
// Scenario 1: The local event has not been sent yet to the server.
{
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
let event = item.as_event().unwrap();
assert_matches!(event.send_state(), Some(EventSendState::NotSentYet));
}
// Scenario 2: The local event has not been sent to the server successfully, it
// has failed. In this case, there is no event ID.
{
let some_io_error = Error::Io(io::Error::new(io::ErrorKind::Other, "this is a test"));
timeline
.inner
.update_event_send_state(
&txn_id,
EventSendState::SendingFailed { error: Arc::new(some_io_error) },
)
.await;
let item = assert_next_matches!(stream, VectorDiff::Set { value, index: 1 } => value);
let event = item.as_event().unwrap();
assert_matches!(event.send_state(), Some(EventSendState::SendingFailed { .. }));
}
// Scenario 3: The local event has been sent successfully to the server and an
// event ID has been received as part of the server's response.
let event_id = event_id!("$W6mZSLWMmfuQQ9jhZWeTxFIM");
let timestamp = {
timeline
.inner
.update_event_send_state(
&txn_id,
EventSendState::Sent { event_id: event_id.to_owned() },
)
.await;
let item = assert_next_matches!(stream, VectorDiff::Set { value, index: 1 } => value);
let event_item = item.as_event().unwrap();
assert_matches!(event_item.send_state(), Some(EventSendState::Sent { .. }));
event_item.timestamp()
};
// Now, a sync has been run against the server, and an event with the same ID
// comes in.
timeline
.handle_live_custom_event(json!({
"content": {
"body": "echo",
"msgtype": "m.text",
},
"sender": &*ALICE,
"event_id": event_id,
"origin_server_ts": timestamp,
"type": "m.room.message",
}))
.await;
// The local echo is replaced with the remote echo
let item = assert_next_matches!(stream, VectorDiff::Set { index: 1, value } => value);
assert!(!item.as_event().unwrap().is_local_echo());
}
#[async_test]
async fn remote_echo_new_position() {
let timeline = TestTimeline::new();
let mut stream = timeline.subscribe().await;
// Given a local event…
let txn_id = timeline
.handle_local_event(AnyMessageLikeEventContent::RoomMessage(
RoomMessageEventContent::text_plain("echo"),
))
.await;
let _day_divider = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
let txn_id_from_event = item.as_event().unwrap();
assert_eq!(txn_id, txn_id_from_event.transaction_id().unwrap());
// … and another event that comes back before the remote echo
timeline.handle_live_message_event(&BOB, RoomMessageEventContent::text_plain("test")).await;
let _day_divider = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
let _bob_message = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
// When the remote echo comes in…
timeline
.handle_live_custom_event(json!({
"content": {
"body": "echo",
"msgtype": "m.text",
},
"sender": &*ALICE,
"event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I",
"origin_server_ts": 6,
"type": "m.room.message",
"unsigned": {
"transaction_id": txn_id,
},
}))
.await;
// … the local echo should be removed
assert_next_matches!(stream, VectorDiff::Remove { index: 1 });
// … along with its day divider
assert_next_matches!(stream, VectorDiff::Remove { index: 0 });
// … and the remote echo added (no new day divider because both bob's and
// alice's message are from the same day according to server timestamps)
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
assert!(!item.as_event().unwrap().is_local_echo());
}