sdk-ui: Move the event-fetching logic for edit and redact functions to the sdk-ui crate where they can be tested, to the edit_by_id and redact_by_id functions.

Added some tests for those, based on the existing ones.
This commit is contained in:
Jorge Martín
2024-09-20 13:27:34 +02:00
committed by Jorge Martin Espinosa
parent 67df36f733
commit 548c66750f
9 changed files with 526 additions and 67 deletions

View File

@@ -489,18 +489,7 @@ impl Timeline {
/// Returns whether the edit did happen. It can only return false for
/// local events that are being processed.
pub async fn edit(&self, id: String, new_content: EditedContent) -> Result<bool, ClientError> {
let event = if let Ok(event_id) = EventId::parse(&id) {
self.inner.item_by_event_id(&event_id).await
} else {
let transaction_id: OwnedTransactionId = id.into();
self.inner.local_item_by_transaction_id(&transaction_id).await
};
if let Some(event) = event {
let new_content: SdkEditedContent = new_content.try_into()?;
self.inner.edit(&event, new_content).await.map_err(ClientError::from)
} else {
Ok(false)
}
self.inner.edit_by_id(&(id.into()), new_content.try_into()?).await.map_err(Into::into)
}
pub async fn send_location(
@@ -604,30 +593,13 @@ impl Timeline {
/// being sent already. If the event was a remote event, then it will be
/// redacted by sending a redaction request to the server.
///
/// Returns whether the redaction did happen. It can only return false for
/// local events that are being processed.
/// Will return an error if the event couldn't be redacted.
pub async fn redact_event(
&self,
id: String,
reason: Option<String>,
) -> Result<bool, ClientError> {
let event = if let Ok(event_id) = EventId::parse(&id) {
self.inner.item_by_event_id(&event_id).await
} else {
let transaction_id: OwnedTransactionId = id.into();
self.inner.local_item_by_transaction_id(&transaction_id).await
};
if let Some(event) = event {
let removed = self
.inner
.redact(&event, reason.as_deref())
.await
.map_err(|err| anyhow::anyhow!(err))?;
Ok(removed)
} else {
Ok(false)
}
) -> Result<(), ClientError> {
self.inner.redact_by_id(&(id.into()), reason.as_deref()).await.map_err(Into::into)
}
/// Load the reply details for the given event id.

View File

@@ -1380,9 +1380,12 @@ impl TimelineController {
#[instrument(skip(self))]
pub(super) async fn fetch_in_reply_to_details(&self, event_id: &EventId) -> Result<(), Error> {
let state = self.state.write().await;
let (index, item) =
rfind_event_by_id(&state.items, event_id).ok_or(Error::RemoteEventNotInTimeline)?;
let remote_item = item.as_remote().ok_or(Error::RemoteEventNotInTimeline)?.clone();
let (index, item) = rfind_event_by_id(&state.items, event_id)
.ok_or(Error::EventNotInTimeline(TimelineEventItemId::EventId(event_id.to_owned())))?;
let remote_item = item
.as_remote()
.ok_or(Error::EventNotInTimeline(TimelineEventItemId::EventId(event_id.to_owned())))?
.clone();
let TimelineItemContent::Message(message) = item.content().clone() else {
debug!("Event is not a message");
@@ -1418,7 +1421,7 @@ impl TimelineController {
// changed while waiting for the request.
let mut state = self.state.write().await;
let (index, item) = rfind_event_by_id(&state.items, &remote_item.event_id)
.ok_or(Error::RemoteEventNotInTimeline)?;
.ok_or(Error::EventNotInTimeline(TimelineEventItemId::EventId(event_id.to_owned())))?;
// Check the state of the event again, it might have been redacted while
// the request was in-flight.

View File

@@ -18,17 +18,18 @@ use matrix_sdk::{
send_queue::RoomSendQueueError,
HttpError,
};
use ruma::OwnedTransactionId;
use thiserror::Error;
use crate::timeline::pinned_events_loader::PinnedEventsLoaderError;
use crate::timeline::{pinned_events_loader::PinnedEventsLoaderError, TimelineEventItemId};
/// Errors specific to the timeline.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
/// The requested event with a remote echo is not in the timeline.
#[error("Event with remote echo not found in timeline")]
RemoteEventNotInTimeline,
/// The requested event is not in the timeline.
#[error("Event not found in timeline: {0:?}")]
EventNotInTimeline(TimelineEventItemId),
/// The event is currently unsupported for this use case..
#[error("Unsupported event")]
@@ -76,7 +77,18 @@ pub enum Error {
/// An error happened while attempting to redact an event.
#[error(transparent)]
RedactError(HttpError),
RedactError(RedactError),
}
#[derive(Error, Debug)]
pub enum RedactError {
/// Local event to redact wasn't found for transaction id
#[error("Local event to redact wasn't found for transaction {0}")]
LocalEventNotFound(OwnedTransactionId),
/// An error happened while attempting to redact an event.
#[error(transparent)]
HttpError(#[from] HttpError),
}
#[derive(Error, Debug)]

View File

@@ -99,6 +99,22 @@ pub enum TimelineEventItemId {
EventId(OwnedEventId),
}
impl From<String> for TimelineEventItemId {
fn from(value: String) -> Self {
value.as_str().into()
}
}
impl From<&str> for TimelineEventItemId {
fn from(value: &str) -> Self {
if let Ok(event_id) = EventId::parse(value) {
TimelineEventItemId::EventId(event_id)
} else {
TimelineEventItemId::TransactionId(value.into())
}
}
}
/// An handle that usually allows to perform an action on a timeline event.
///
/// If the item represents a remote item, then the event id is usually
@@ -251,7 +267,7 @@ impl EventTimelineItem {
/// Returns the transaction ID for a local echo item that has not been sent
/// and the event ID for a local echo item that has been sent or a
/// remote item.
pub(crate) fn identifier(&self) -> TimelineEventItemId {
pub fn identifier(&self) -> TimelineEventItemId {
match &self.kind {
EventTimelineItemKind::Local(local) => local.identifier(),
EventTimelineItemKind::Remote(remote) => {
@@ -719,7 +735,7 @@ mod tests {
};
use super::{EventTimelineItem, Profile};
use crate::timeline::TimelineDetails;
use crate::timeline::{TimelineDetails, TimelineEventItemId};
#[async_test]
async fn test_latest_message_event_can_be_wrapped_as_a_timeline_item() {
@@ -833,6 +849,20 @@ mod tests {
);
}
#[test]
fn test_raw_event_id_into_timeline_event_item_id_gets_event_id() {
let raw_id = "$123:example.com";
let id: TimelineEventItemId = raw_id.into();
assert_matches!(id, TimelineEventItemId::EventId(_));
}
#[test]
fn test_raw_str_into_timeline_event_item_id_gets_transaction_id() {
let raw_id = "something something";
let id: TimelineEventItemId = raw_id.into();
assert_matches!(id, TimelineEventItemId::TransactionId(_));
}
fn member_event(
room_id: &RoomId,
user_id: &UserId,

View File

@@ -230,6 +230,17 @@ impl Timeline {
self.controller.retry_event_decryption(self.room(), None).await;
}
/// Get the current timeline item for the given [`TimelineEventItemId`], if
/// any.
async fn event_by_timeline_id(&self, id: &TimelineEventItemId) -> Option<EventTimelineItem> {
match id {
TimelineEventItemId::EventId(event_id) => self.item_by_event_id(event_id).await,
TimelineEventItemId::TransactionId(transaction_id) => {
self.item_by_transaction_id(transaction_id).await
}
}
}
/// Get the current timeline item for the given event ID, if any.
///
/// Will return a remote event, *or* a local echo that has been sent but not
@@ -455,6 +466,21 @@ impl Timeline {
Some(found.clone())
}
/// Edit an event given its [`TimelineEventItemId`] and some new content.
///
/// See [`Self::edit`] for more information.
pub async fn edit_by_id(
&self,
id: &TimelineEventItemId,
new_content: EditedContent,
) -> Result<bool, Error> {
let Some(event) = self.event_by_timeline_id(id).await else {
return Err(Error::EventNotInTimeline(id.clone()));
};
self.edit(&event, new_content).await
}
/// Edit an event.
///
/// Only supports events for which [`EventTimelineItem::is_editable()`]
@@ -568,6 +594,38 @@ impl Timeline {
SendAttachment::new(self, path.into(), mime_type, config)
}
/// Redact an event given its [`TimelineEventItemId`] and an optional
/// reason.
///
/// See [`Self::redact`] for more info.
pub async fn redact_by_id(
&self,
id: &TimelineEventItemId,
reason: Option<&str>,
) -> Result<(), Error> {
match id {
TimelineEventItemId::TransactionId(transaction_id) => {
let Some(event) = self.item_by_transaction_id(transaction_id).await else {
return Err(Error::RedactError(RedactError::LocalEventNotFound(
transaction_id.to_owned(),
)));
};
let TimelineItemHandle::Local(handle) = event.handle() else {
panic!("If the item is local, this should never happen");
};
handle.abort().await.map_err(RoomSendQueueError::StorageError)?;
}
TimelineEventItemId::EventId(event_id) => {
self.room()
.redact(event_id, reason, None)
.await
.map_err(RedactError::HttpError)
.map_err(Error::RedactError)?;
}
}
Ok(())
}
/// Redact an event.
///
/// # Returns
@@ -583,28 +641,27 @@ impl Timeline {
reason: Option<&str>,
) -> Result<bool, Error> {
let event_id = match event.identifier() {
TimelineEventItemId::TransactionId(txn_id) => {
TimelineEventItemId::TransactionId(_) => {
// See if we have an up-to-date timeline item with that transaction id.
if let Some(item) = self.item_by_transaction_id(&txn_id).await {
match item.handle() {
TimelineItemHandle::Remote(event_id) => event_id.to_owned(),
TimelineItemHandle::Local(handle) => {
return Ok(handle
.abort()
.await
.map_err(RoomSendQueueError::StorageError)?);
}
match event.handle() {
TimelineItemHandle::Remote(event_id) => event_id.to_owned(),
TimelineItemHandle::Local(handle) => {
return Ok(handle
.abort()
.await
.map_err(RoomSendQueueError::StorageError)?);
}
} else {
warn!("Couldn't find the local echo anymore, nor a matching remote echo");
return Ok(false);
}
}
TimelineEventItemId::EventId(event_id) => event_id,
};
self.room().redact(&event_id, reason, None).await.map_err(Error::RedactError)?;
self.room()
.redact(&event_id, reason, None)
.await
.map_err(RedactError::HttpError)
.map_err(Error::RedactError)?;
Ok(true)
}

View File

@@ -36,7 +36,7 @@ use ruma::{
};
use tracing::{debug, error};
use super::{Profile, TimelineBuilder};
use super::{Profile, RedactError, TimelineBuilder};
use crate::timeline::{self, pinned_events_loader::PinnedEventsRoom, Timeline};
pub trait RoomExt {
@@ -269,6 +269,7 @@ impl RoomDataProvider for Room {
let _ = self
.redact(event_id, reason, transaction_id)
.await
.map_err(RedactError::HttpError)
.map_err(super::Error::RedactError)?;
Ok(())
}

View File

@@ -29,7 +29,9 @@ use matrix_sdk_test::{
async_test, mocks::mock_encryption_state, JoinedRoomBuilder, SyncResponseBuilder, ALICE, BOB,
};
use matrix_sdk_ui::{
timeline::{EventSendState, RoomExt, TimelineDetails, TimelineItemContent},
timeline::{
Error, EventSendState, RoomExt, TimelineDetails, TimelineEventItemId, TimelineItemContent,
},
Timeline,
};
use ruma::{
@@ -46,7 +48,7 @@ use ruma::{
},
AnyMessageLikeEventContent, AnyTimelineEvent,
},
room_id,
owned_event_id, room_id,
serde::Raw,
OwnedRoomId,
};
@@ -1092,3 +1094,263 @@ async fn test_pending_poll_edit() {
// And nothing else.
assert!(timeline_stream.next().now_or_never().is_none());
}
#[async_test]
async fn test_send_edit_by_event_id() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client_with_server().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
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(sync_settings.clone()).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 (_, mut timeline_stream) =
timeline.subscribe_filter_map(|item| item.as_event().cloned()).await;
let f = EventFactory::new();
sync_builder.add_joined_room(
JoinedRoomBuilder::new(room_id).add_timeline_event(
f.text_msg("Hello, World!")
.sender(client.user_id().unwrap())
.event_id(event_id!("$original_event")),
),
);
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
server.reset().await;
let hello_world_item =
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => value);
let hello_world_message = hello_world_item.content().as_message().unwrap();
assert!(!hello_world_message.is_edited());
assert!(hello_world_item.is_editable());
mock_encryption_state(&server, false).await;
Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*"))
.respond_with(
ResponseTemplate::new(200).set_body_json(json!({ "event_id": "$edit_event" })),
)
.expect(1)
.mount(&server)
.await;
timeline
.edit_by_id(
&hello_world_item.identifier(),
EditedContent::RoomMessage(RoomMessageEventContentWithoutRelation::text_plain(
"Hello, Room!",
)),
)
.await
.unwrap();
// Let the send queue handle the event.
yield_now().await;
let edit_item =
assert_next_matches!(timeline_stream, VectorDiff::Set { index: 0, value } => value);
// The event itself is already known to the server. We don't currently have
// a separate edit send state.
assert_matches!(edit_item.send_state(), None);
let edit_message = edit_item.content().as_message().unwrap();
assert_eq!(edit_message.body(), "Hello, Room!");
assert!(edit_message.is_edited());
// The response to the mocked endpoint does not generate further timeline
// updates, so just wait for a bit before verifying that the endpoint was
// called.
sleep(Duration::from_millis(200)).await;
server.verify().await;
}
#[async_test]
async fn test_send_edit_by_non_existing_event_id() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client_with_server().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
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(sync_settings.clone()).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();
mock_encryption_state(&server, false).await;
let error = timeline
.edit_by_id(
&TimelineEventItemId::EventId(owned_event_id!("$123:example.com")),
EditedContent::RoomMessage(RoomMessageEventContentWithoutRelation::text_plain(
"Hello, Room!",
)),
)
.await
.err()
.unwrap();
assert_matches!(error, Error::EventNotInTimeline(_));
}
#[async_test]
async fn test_edit_local_echo_by_id() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client_with_server().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
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(sync_settings.clone()).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 (_, mut timeline_stream) = timeline.subscribe().await;
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(sync_settings.clone()).await.unwrap();
server.reset().await;
mock_encryption_state(&server, false).await;
let mounted_send = Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*"))
.and(header("authorization", "Bearer 1234"))
.respond_with(ResponseTemplate::new(413).set_body_json(json!({
"errcode": "M_TOO_LARGE",
})))
.expect(1)
.mount_as_scoped(&server)
.await;
// Redacting a local event works.
timeline.send(RoomMessageEventContent::text_plain("hello, just you").into()).await.unwrap();
assert_let!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next().await);
let internal_id = item.unique_id();
let item = item.as_event().unwrap();
assert_matches!(item.send_state(), Some(EventSendState::NotSentYet));
assert_let!(Some(VectorDiff::PushFront { value: day_divider }) = timeline_stream.next().await);
assert!(day_divider.is_day_divider());
// We haven't set a route for sending events, so this will fail.
assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next().await);
let item = item.as_event().unwrap();
assert!(item.is_local_echo());
assert!(item.is_editable());
assert_matches!(
item.send_state(),
Some(EventSendState::SendingFailed { is_recoverable: false, .. })
);
assert!(timeline_stream.next().now_or_never().is_none());
// Set up the success response before editing, since edit causes an immediate
// retry (the room's send queue is not blocked, since the one event it couldn't
// send failed in an unrecoverable way).
drop(mounted_send);
Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "event_id": "$1" })))
.expect(1)
.mount(&server)
.await;
// Let's edit the local echo.
let did_edit = timeline
.edit_by_id(
&item.identifier(),
EditedContent::RoomMessage(RoomMessageEventContent::text_plain("hello, world").into()),
)
.await
.unwrap();
// We could edit the local echo, since it was in the failed state.
assert!(did_edit);
// Observe local echo being replaced.
assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next().await);
assert_eq!(item.unique_id(), internal_id);
let item = item.as_event().unwrap();
assert!(item.is_local_echo());
// The send state has been reset.
assert_matches!(item.send_state(), Some(EventSendState::NotSentYet));
let edit_message = item.content().as_message().unwrap();
assert_eq!(edit_message.body(), "hello, world");
// Observe the event being sent, and replacing the local echo.
assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next().await);
let item = item.as_event().unwrap();
assert!(item.is_local_echo());
let edit_message = item.content().as_message().unwrap();
assert_eq!(edit_message.body(), "hello, world");
// No new updates.
assert!(timeline_stream.next().now_or_never().is_none());
}
#[async_test]
async fn test_send_edit_by_non_existing_local_id() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client_with_server().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
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(sync_settings.clone()).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();
mock_encryption_state(&server, false).await;
let error = timeline
.edit_by_id(
&TimelineEventItemId::TransactionId("something".into()),
EditedContent::RoomMessage(RoomMessageEventContentWithoutRelation::text_plain(
"Hello, Room!",
)),
)
.await
.err()
.unwrap();
assert_matches!(error, Error::EventNotInTimeline(_));
}

View File

@@ -35,18 +35,14 @@ use matrix_sdk_ui::{
},
RoomListService, Timeline,
};
use ruma::{
event_id,
events::room::{encryption::RoomEncryptionEventContent, message::RoomMessageEventContent},
room_id, user_id, MilliSecondsSinceUnixEpoch,
};
use ruma::{event_id, events::room::{encryption::RoomEncryptionEventContent, message::RoomMessageEventContent}, owned_event_id, room_id, user_id, MilliSecondsSinceUnixEpoch};
use serde_json::json;
use stream_assert::{assert_next_matches, assert_pending};
use wiremock::{
matchers::{header, method, path_regex},
Mock, ResponseTemplate,
};
use matrix_sdk_ui::timeline::{Error, RedactError, TimelineEventItemId};
use crate::mock_sync;
mod echo;
@@ -307,6 +303,132 @@ async fn test_redact_message() {
assert_matches!(timeline_stream.next().await, Some(VectorDiff::Remove { index: 2 }));
}
#[async_test]
async fn test_redact_by_id_message() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client_with_server().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
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(sync_settings.clone()).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 (_, mut timeline_stream) = timeline.subscribe().await;
let factory = EventFactory::new();
factory.set_next_ts(MilliSecondsSinceUnixEpoch::now().get().into());
sync_builder.add_joined_room(
JoinedRoomBuilder::new(room_id).add_timeline_event(
factory.sender(user_id!("@a:b.com")).text_msg("buy my bitcoins bro"),
),
);
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
server.reset().await;
assert_let!(Some(VectorDiff::PushBack { value: first }) = timeline_stream.next().await);
assert_eq!(
first.as_event().unwrap().content().as_message().unwrap().body(),
"buy my bitcoins bro"
);
assert_let!(Some(VectorDiff::PushFront { value: day_divider }) = timeline_stream.next().await);
assert!(day_divider.is_day_divider());
// Redacting a remote event works.
mock_redaction(event_id!("$42")).mount(&server).await;
let event = first.as_event().unwrap();
timeline.redact_by_id(&event.identifier(), Some("inapprops")).await.unwrap();
// Redacting a local event works.
timeline
.send(RoomMessageEventContent::text_plain("i will disappear soon").into())
.await
.unwrap();
assert_let!(Some(VectorDiff::PushBack { value: second }) = timeline_stream.next().await);
let second = second.as_event().unwrap();
assert_matches!(second.send_state(), Some(EventSendState::NotSentYet));
// We haven't set a route for sending events, so this will fail.
assert_let!(Some(VectorDiff::Set { index, value: second }) = timeline_stream.next().await);
assert_eq!(index, 2);
let second = second.as_event().unwrap();
assert!(second.is_local_echo());
assert_matches!(second.send_state(), Some(EventSendState::SendingFailed { .. }));
// Let's redact the local echo.
timeline.redact_by_id(&second.identifier(), None).await.unwrap();
// Observe local echo being removed.
assert_matches!(timeline_stream.next().await, Some(VectorDiff::Remove { index: 2 }));
}
#[async_test]
async fn test_redact_by_id_message_with_no_remote_message_present() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client_with_server().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
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(sync_settings.clone()).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 error = timeline
.redact_by_id(&TimelineEventItemId::EventId(owned_event_id!("$123:example.com")), None)
.await
.err();
assert_matches!(error, Some(Error::RedactError(RedactError::HttpError(_))))
}
#[async_test]
async fn test_redact_by_id_message_with_no_local_message_present() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client_with_server().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
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(sync_settings.clone()).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 error = timeline
.redact_by_id(&TimelineEventItemId::TransactionId("something".into()), None)
.await
.err();
assert_matches!(error, Some(Error::RedactError(RedactError::LocalEventNotFound(_))))
}
#[async_test]
async fn test_read_marker() {
let room_id = room_id!("!a98sd12bjh:example.org");

View File

@@ -61,7 +61,7 @@ async fn test_in_reply_to_details() {
// The event doesn't exist.
assert_matches!(
timeline.fetch_details_for_event(event_id!("$fakeevent")).await,
Err(TimelineError::RemoteEventNotInTimeline)
Err(TimelineError::EventNotInTimeline(_))
);
// Add an event and a reply to that event to the timeline