test: add a test for redacting a reaction that was being sent

This commit is contained in:
Benjamin Bouvier
2024-08-21 16:58:07 +02:00
parent 6dc8d3980e
commit cdd6c23e15
2 changed files with 174 additions and 0 deletions

View File

@@ -48,6 +48,7 @@ mod pagination;
mod pinned_event;
mod profiles;
mod queue;
mod reactions;
mod read_receipts;
mod replies;
mod subscribe;

View File

@@ -0,0 +1,173 @@
// Copyright 2024 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::time::Duration;
use assert_matches2::{assert_let, assert_matches};
use eyeball_im::VectorDiff;
use futures_util::{FutureExt as _, StreamExt as _};
use matrix_sdk::test_utils::{events::EventFactory, logged_in_client_with_server};
use matrix_sdk_test::{
async_test,
mocks::{mock_encryption_state, mock_redaction},
JoinedRoomBuilder, SyncResponseBuilder, ALICE,
};
use matrix_sdk_ui::timeline::{ReactionStatus, RoomExt as _};
use ruma::{event_id, events::relation::Annotation, room_id};
use serde_json::json;
use wiremock::{
matchers::{header, method, path_regex},
Mock, ResponseTemplate,
};
use crate::mock_sync;
#[async_test]
async fn test_abort_before_being_sent() {
// This test checks that a reaction could be aborted *before* or *while* it's
// being sent by the send queue.
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client_with_server().await;
let user_id = client.user_id().unwrap();
// Make the test aware of the room.
let mut sync_builder = SyncResponseBuilder::new();
sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(Default::default()).await.unwrap();
server.reset().await;
mock_encryption_state(&server, false).await;
let room = client.get_room(room_id).unwrap();
let timeline = room.timeline().await.unwrap();
let (initial_items, mut stream) = timeline.subscribe().await;
assert!(initial_items.is_empty());
let f = EventFactory::new();
let event_id = event_id!("$1");
sync_builder.add_joined_room(
JoinedRoomBuilder::new(room_id)
.add_timeline_event(f.text_msg("hello").sender(&ALICE).event_id(event_id)),
);
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(Default::default()).await.unwrap();
server.reset().await;
assert_let!(Some(VectorDiff::PushBack { value: first }) = stream.next().await);
let item = first.as_event().unwrap();
assert_eq!(item.content().as_message().unwrap().body(), "hello");
assert_let!(Some(VectorDiff::PushFront { value: day_divider }) = stream.next().await);
assert!(day_divider.is_day_divider());
// Now we try to add two reactions to this message…
// Mock the send endpoint with a delay, to give us time to abort the sending.
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": "$2",
}))
.set_delay(Duration::from_millis(150)),
)
.expect(1)
.named("send")
.mount(&server)
.await;
mock_redaction(event_id!("$3")).mount(&server).await;
// We add the reaction…
timeline.toggle_reaction(&Annotation::new(event_id.to_owned(), "👍".to_owned())).await.unwrap();
// First toggle (local echo).
{
assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = stream.next().await);
let reactions = item.as_event().unwrap().reactions();
assert_eq!(reactions.len(), 1);
assert_matches!(
&reactions.get("👍").unwrap().get(user_id).unwrap().status,
ReactionStatus::LocalToRemote(_)
);
assert!(stream.next().now_or_never().is_none());
}
// We toggle another reaction at the same time…
timeline.toggle_reaction(&Annotation::new(event_id.to_owned(), "🥰".to_owned())).await.unwrap();
{
assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = stream.next().await);
let reactions = item.as_event().unwrap().reactions();
assert_eq!(reactions.len(), 2);
assert_matches!(
&reactions.get("👍").unwrap().get(user_id).unwrap().status,
ReactionStatus::LocalToRemote(_)
);
assert_matches!(
&reactions.get("🥰").unwrap().get(user_id).unwrap().status,
ReactionStatus::LocalToRemote(_)
);
assert!(stream.next().now_or_never().is_none());
}
// Then we remove the first one; because it was being sent, it should lead to a
// redaction event.
timeline.toggle_reaction(&Annotation::new(event_id.to_owned(), "👍".to_owned())).await.unwrap();
{
assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = stream.next().await);
let reactions = item.as_event().unwrap().reactions();
assert_eq!(reactions.len(), 1);
assert_matches!(
&reactions.get("🥰").unwrap().get(user_id).unwrap().status,
ReactionStatus::LocalToRemote(_)
);
assert!(stream.next().now_or_never().is_none());
}
// But because the first one was being sent, this one won't and the local echo
// could be discarded.
timeline.toggle_reaction(&Annotation::new(event_id.to_owned(), "🥰".to_owned())).await.unwrap();
{
assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = stream.next().await);
let reactions = item.as_event().unwrap().reactions();
assert!(reactions.is_empty());
assert!(stream.next().now_or_never().is_none());
}
// In a real-world setup with a background sync, the reaction may flash to the
// user, because the sync may return the reaction event, and later the
// redaction of the reaction. In our case, we're done here.
tokio::time::sleep(Duration::from_millis(300)).await;
assert!(stream.next().now_or_never().is_none());
}