From f6faf6267ec915228ea3e278ebee1f14901f97d3 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 13 Aug 2024 10:29:56 +0200 Subject: [PATCH] send queue: add integration test for sending and aborting reactions --- .../tests/integration/send_queue.rs | 164 +++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk/tests/integration/send_queue.rs b/crates/matrix-sdk/tests/integration/send_queue.rs index c7a9c7a6e..53494791f 100644 --- a/crates/matrix-sdk/tests/integration/send_queue.rs +++ b/crates/matrix-sdk/tests/integration/send_queue.rs @@ -85,6 +85,26 @@ macro_rules! assert_update { (txn, send_handle) }}; + // Check the next stream event is a local echo for a reaction with the content $key which + // applies to the local echo with transaction id $parent. + ($watch:ident => local reaction { key = $key:expr, parent = $parent_txn_id:expr }) => {{ + assert_let!( + Ok(Ok(RoomSendQueueUpdate::NewLocalEvent(LocalEcho { + content: LocalEchoContent::React { + key, + applies_to, + send_handle: _, + }, + transaction_id: txn, + }))) = timeout(Duration::from_secs(1), $watch.recv()).await + ); + + assert_eq!(key, $key); + assert_eq!(applies_to, $parent_txn_id); + + txn + }}; + // Check the next stream event is an edit for a local echo with the content $body, and that the // transaction id is the one we expect. ($watch:ident => edit { body = $body:expr, txn = $transaction_id:expr }) => {{ @@ -759,7 +779,8 @@ async fn test_cancellation() { // Let the background task start now. tokio::task::yield_now().await; - // While the first item is being sent, the system records the intent to edit it. + // While the first item is being sent, the system records the intent to abort + // it. assert!(handle1.abort().await.unwrap()); assert_update!(watch => cancelled { txn = txn1 }); assert!(watch.is_empty()); @@ -1203,6 +1224,8 @@ async fn test_abort_or_edit_after_send() { .not()); // Neither will aborting. assert!(handle.abort().await.unwrap().not()); + // Or sending a reaction. + assert!(handle.react("😊".to_owned()).await.unwrap().is_none()); assert!(watch.is_empty()); } @@ -1541,3 +1564,142 @@ async fn test_reloading_rooms_with_unsent_events() { // The real assertion is on the expect(2) on the above Mock. server.verify().await; } + +#[async_test] +async fn test_reactions() { + let (client, server) = logged_in_client_with_server().await; + + // Mark the room as joined. + let room_id = room_id!("!a:b.c"); + + let room = mock_sync_with_new_room( + |builder| { + builder.add_joined_room(JoinedRoomBuilder::new(room_id)); + }, + &client, + &server, + room_id, + ) + .await; + + let q = room.send_queue(); + + let (local_echoes, mut watch) = q.subscribe().await.unwrap(); + assert!(local_echoes.is_empty()); + assert!(watch.is_empty()); + + let lock = Arc::new(Mutex::new(0)); + let lock_guard = lock.lock().await; + + let mock_lock = lock.clone(); + + 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(move |_req: &Request| { + // Wait for the signal from the main thread that we can process this query. + let mock_lock = mock_lock.clone(); + let event_id = std::thread::spawn(move || { + tokio::runtime::Runtime::new().unwrap().block_on(async { + let mut event_id = mock_lock.lock().await; + let ret = *event_id; + *event_id += 1; + ret + }) + }) + .join() + .unwrap(); + + ResponseTemplate::new(200).set_body_json(json!({ + "event_id": format!("${event_id}"), + })) + }) + .expect(3) + .mount(&server) + .await; + + // Sending of the second emoji has started; abort it, it will result in a redact + // request. + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({"event_id": "$3"}))) + .expect(1) + .mount(&server) + .await; + + // Send a message. + let msg_handle = + room.send_queue().send(RoomMessageEventContent::text_plain("1").into()).await.unwrap(); + + // React to it a few times. + let emoji_handle = + msg_handle.react("💯".to_owned()).await.unwrap().expect("first emoji was queued"); + let emoji_handle2 = + msg_handle.react("🍭".to_owned()).await.unwrap().expect("second emoji was queued"); + let emoji_handle3 = + msg_handle.react("👍".to_owned()).await.unwrap().expect("fourth emoji was queued"); + + let (txn1, _) = assert_update!(watch => local echo { body = "1" }); + let emoji1_txn = assert_update!(watch => local reaction { key = "💯", parent = txn1 }); + let emoji2_txn = assert_update!(watch => local reaction { key = "🍭", parent = txn1 }); + let emoji3_txn = assert_update!(watch => local reaction { key = "👍", parent = txn1 }); + + { + let (local_echoes, _) = q.subscribe().await.unwrap(); + + assert_eq!(local_echoes.len(), 4); + assert_eq!(local_echoes[0].transaction_id, txn1); + + assert_eq!(local_echoes[1].transaction_id, emoji1_txn); + assert_let!(LocalEchoContent::React { key, applies_to, .. } = &local_echoes[1].content); + assert_eq!(key, "💯"); + assert_eq!(*applies_to, txn1); + + assert_eq!(local_echoes[2].transaction_id, emoji2_txn); + assert_let!(LocalEchoContent::React { key, applies_to, .. } = &local_echoes[2].content); + assert_eq!(key, "🍭"); + assert_eq!(*applies_to, txn1); + + assert_eq!(local_echoes[3].transaction_id, emoji3_txn); + assert_let!(LocalEchoContent::React { key, applies_to, .. } = &local_echoes[3].content); + assert_eq!(key, "👍"); + assert_eq!(*applies_to, txn1); + } + + // Cancel the first reaction before the original event is sent. + let aborted = emoji_handle.abort().await.unwrap(); + assert!(aborted); + assert_update!(watch => cancelled { txn = emoji1_txn }); + assert!(watch.is_empty()); + + // Let the original event be sent, and re-take the lock immediately so no + // reactions aren't sent (since the lock is fair). + drop(lock_guard); + assert_update!(watch => sent { txn = txn1, event_id = event_id!("$0") }); + let lock_guard = lock.lock().await; + assert!(watch.is_empty()); + + // Abort sending of the second emoji. It was being sent, so it's first cancelled + // *then* sent and redacted. + let aborted = emoji_handle2.abort().await.unwrap(); + assert!(aborted); + assert_update!(watch => cancelled { txn = emoji2_txn }); + assert!(watch.is_empty()); + + // Drop the guard to let the mock server process events. + drop(lock_guard); + + // Previous emoji has been sent; it will be redacted later. + assert_update!(watch => sent { txn = emoji2_txn, event_id = event_id!("$1") }); + + // The final emoji is sent. + assert_update!(watch => sent { txn = emoji3_txn, event_id = event_id!("$2") }); + + // Cancelling sending of the third emoji fails because it's been sent already. + assert!(emoji_handle3.abort().await.unwrap().not()); + + assert!(watch.is_empty()); +}