mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-04 05:58:11 -04:00
tests(notification): add integration tests for notification events
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4668,7 +4668,9 @@ dependencies = [
|
||||
"futures-util",
|
||||
"matrix-sdk",
|
||||
"matrix-sdk-integration-testing",
|
||||
"matrix-sdk-ui",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ use crate::{config::RequestConfig, error::RumaApiError, http_client::HttpClient,
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClientBuilder {
|
||||
homeserver_cfg: Option<HomeserverConfig>,
|
||||
#[cfg(feature = "experimental-sliding-sync")]
|
||||
sliding_sync_proxy: Option<String>,
|
||||
http_cfg: Option<HttpConfig>,
|
||||
store_config: BuilderStoreConfig,
|
||||
request_config: RequestConfig,
|
||||
@@ -85,6 +87,8 @@ impl ClientBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
homeserver_cfg: None,
|
||||
#[cfg(feature = "experimental-sliding-sync")]
|
||||
sliding_sync_proxy: None,
|
||||
http_cfg: None,
|
||||
store_config: BuilderStoreConfig::Custom(StoreConfig::default()),
|
||||
request_config: Default::default(),
|
||||
@@ -106,6 +110,18 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the sliding-sync proxy URL to use.
|
||||
///
|
||||
/// This is used only if the homeserver URL was defined with
|
||||
/// [`Self::homeserver_url`]. If the homeserver address was defined with
|
||||
/// [`Self::server_name`], then auto-discovery via the `.well-known`
|
||||
/// endpoint will be performed.
|
||||
#[cfg(feature = "experimental-sliding-sync")]
|
||||
pub fn sliding_sync_proxy(mut self, url: impl AsRef<str>) -> Self {
|
||||
self.sliding_sync_proxy = Some(url.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the server name to discover the homeserver from.
|
||||
///
|
||||
/// We assume we can connect in HTTPS to that server. If that's not the
|
||||
@@ -383,7 +399,14 @@ impl ClientBuilder {
|
||||
let mut sliding_sync_proxy: Option<Url> = None;
|
||||
|
||||
let homeserver = match homeserver_cfg {
|
||||
HomeserverConfig::Url(url) => url,
|
||||
HomeserverConfig::Url(url) => {
|
||||
#[cfg(feature = "experimental-sliding-sync")]
|
||||
{
|
||||
sliding_sync_proxy =
|
||||
self.sliding_sync_proxy.as_ref().map(|url| Url::parse(url)).transpose()?;
|
||||
}
|
||||
url
|
||||
}
|
||||
HomeserverConfig::ServerName { server: server_name, protocol } => {
|
||||
debug!("Trying to discover the homeserver");
|
||||
|
||||
|
||||
@@ -28,28 +28,25 @@ fn init_logging() {
|
||||
.init();
|
||||
}
|
||||
|
||||
/// read the test configuration from the environment
|
||||
pub fn test_server_conf() -> (String, String) {
|
||||
(
|
||||
option_env!("HOMESERVER_URL").unwrap_or("http://localhost:8228").to_owned(),
|
||||
option_env!("HOMESERVER_DOMAIN").unwrap_or("matrix-sdk.rs").to_owned(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get_client_for_user(username: String, use_sqlite_store: bool) -> Result<Client> {
|
||||
let mut users = USERS.lock().await;
|
||||
if let Some((client, _)) = users.get(&username) {
|
||||
return Ok(client.clone());
|
||||
}
|
||||
|
||||
let (homeserver_url, _domain_name) = test_server_conf();
|
||||
let homeserver_url =
|
||||
option_env!("HOMESERVER_URL").unwrap_or("http://localhost:8228").to_owned();
|
||||
let sliding_sync_proxy_url =
|
||||
option_env!("SLIDING_SYNC_PROXY_URL").unwrap_or("http://localhost:8338").to_owned();
|
||||
|
||||
let tmp_dir = tempdir()?;
|
||||
|
||||
let client_builder = Client::builder()
|
||||
.user_agent("matrix-sdk-integration-tests")
|
||||
.homeserver_url(homeserver_url)
|
||||
.sliding_sync_proxy(sliding_sync_proxy_url)
|
||||
.request_config(RequestConfig::short_retry());
|
||||
|
||||
let client = if use_sqlite_store {
|
||||
client_builder.sqlite_store(tmp_dir.path(), None).build().await?
|
||||
} else {
|
||||
|
||||
@@ -12,5 +12,7 @@ eyeball-im = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
matrix-sdk-integration-testing = { path = "../matrix-sdk-integration-testing", features = ["helpers"] }
|
||||
matrix-sdk = { path = "../../crates/matrix-sdk", features = ["experimental-sliding-sync", "testing"] }
|
||||
matrix-sdk-ui = { path = "../../crates/matrix-sdk-ui" }
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] }
|
||||
tracing = { workspace = true }
|
||||
uuid = { version = "1.2.2" }
|
||||
|
||||
@@ -4,6 +4,8 @@ export SYNAPSE_SERVER_NAME=matrix-sdk.rs
|
||||
export SYNAPSE_REPORT_STATS=no
|
||||
echo " ====== Generating config ====== "
|
||||
/start.py generate
|
||||
|
||||
# Courtesy of https://github.com/michaelkaye/setup-matrix-synapse/blob/e2067245b5265640f94d310fc79a1d388cc70452/create.js#L99
|
||||
echo " ====== Patching for CI ====== "
|
||||
echo """
|
||||
enable_registration: true
|
||||
@@ -16,11 +18,33 @@ rc_message:
|
||||
rc_registration:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
|
||||
|
||||
rc_login:
|
||||
address:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
account:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
failed_attempts:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
|
||||
rc_admin_redaction:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
|
||||
rc_joins:
|
||||
local:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
remote:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
|
||||
rc_3pid_validation:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
|
||||
rc_invites:
|
||||
per_room:
|
||||
@@ -32,18 +56,6 @@ rc_invites:
|
||||
per_issuer:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
|
||||
rc_login:
|
||||
address:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
# account:
|
||||
# per_second: 0.17
|
||||
# burst_count: 3
|
||||
# failed_attempts:
|
||||
# per_second: 0.17
|
||||
# burst_count: 3
|
||||
|
||||
""" >> /data/homeserver.yaml
|
||||
|
||||
echo " ====== Starting server with: ====== "
|
||||
|
||||
@@ -9,7 +9,6 @@ services:
|
||||
disable: true
|
||||
volumes:
|
||||
- ./data/synapse:/data
|
||||
|
||||
ports:
|
||||
- 8228:8008/tcp
|
||||
|
||||
@@ -28,11 +27,10 @@ services:
|
||||
- ./data/db:/var/lib/postgresql/data
|
||||
|
||||
sliding-sync-proxy:
|
||||
image: ghcr.io/matrix-org/sliding-sync:v0.99.2
|
||||
image: ghcr.io/matrix-org/sliding-sync:v0.99.4
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
links:
|
||||
- synapse
|
||||
- postgres
|
||||
|
||||
@@ -5,6 +5,8 @@ use futures_util::{pin_mut, stream::StreamExt};
|
||||
use matrix_sdk::{Client, RoomListEntry, SlidingSyncBuilder, SlidingSyncList, SlidingSyncMode};
|
||||
use matrix_sdk_integration_testing::helpers::get_client_for_user;
|
||||
|
||||
mod notification_client;
|
||||
|
||||
async fn setup(
|
||||
name: String,
|
||||
use_sqlite_store: bool,
|
||||
|
||||
207
testing/sliding-sync-integration-test/src/notification_client.rs
Normal file
207
testing/sliding-sync-integration-test/src/notification_client.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
use assert_matches::assert_matches;
|
||||
use matrix_sdk::{
|
||||
config::SyncSettings,
|
||||
ruma::{
|
||||
api::client::room::create_room::v3::Request as CreateRoomRequest,
|
||||
assign,
|
||||
events::{
|
||||
room::{member::MembershipState, message::RoomMessageEventContent},
|
||||
AnyStrippedStateEvent, SyncMessageLikeEvent, TimelineEventType,
|
||||
},
|
||||
OwnedEventId,
|
||||
},
|
||||
RoomState,
|
||||
};
|
||||
use matrix_sdk_integration_testing::helpers::get_client_for_user;
|
||||
use matrix_sdk_ui::notification_client::{
|
||||
Error, NotificationClient, NotificationEvent, NotificationItem,
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn test_notification() -> Result<()> {
|
||||
// Create new users for each test run, to avoid conflicts with invites existing
|
||||
// from previous runs.
|
||||
let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
|
||||
let alice = get_client_for_user(format!("alice{time}"), true).await?;
|
||||
let bob = get_client_for_user(format!("bob{time}"), true).await?;
|
||||
|
||||
// Alice changes display name.
|
||||
const ALICE_NAME: &str = "Alice, Queen of Cryptography";
|
||||
alice.account().set_display_name(Some(ALICE_NAME)).await?;
|
||||
|
||||
// Initial setup: Alice creates a room, invites Bob.
|
||||
let invite = vec![bob.user_id().expect("bob has a userid!").to_owned()];
|
||||
let request = assign!(CreateRoomRequest::new(), {
|
||||
invite,
|
||||
is_direct: true,
|
||||
});
|
||||
|
||||
let alice_room = alice.create_room(request).await?;
|
||||
|
||||
const ROOM_NAME: &str = "Kingdom of Integration Testing";
|
||||
alice_room.set_name(Some(ROOM_NAME.to_owned())).await?;
|
||||
|
||||
let room_id = alice_room.room_id().to_owned();
|
||||
|
||||
// Bob receives a notification about it.
|
||||
|
||||
let bob_invite_response = bob.sync_once(Default::default()).await?;
|
||||
let sync_token = bob_invite_response.next_batch;
|
||||
|
||||
let mut invited_rooms = bob_invite_response.rooms.invite.into_iter();
|
||||
|
||||
let (_, invited_room) = invited_rooms.next().expect("must be invited to one room");
|
||||
assert!(invited_rooms.next().is_none(), "no more invited rooms: {invited_rooms:#?}");
|
||||
|
||||
if let Some(event_id) = invited_room.invite_state.events.iter().find_map(|event| {
|
||||
let Ok(AnyStrippedStateEvent::RoomMember(room_member_ev)) = event.deserialize() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if room_member_ev.content.membership != MembershipState::Invite {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Ok(Some(event_id)) = event.get_field::<OwnedEventId>("event_id") else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(event_id)
|
||||
}) {
|
||||
warn!("We found the invite event!");
|
||||
|
||||
// Try with sliding sync first.
|
||||
let notification_client = NotificationClient::builder(bob.clone()).await.unwrap().build();
|
||||
let notification = notification_client
|
||||
.get_notification_with_sliding_sync(&room_id, &event_id)
|
||||
.await?
|
||||
.expect("missing notification for the invite");
|
||||
|
||||
warn!("sliding_sync: checking invite notification");
|
||||
|
||||
assert_eq!(notification.event.sender(), alice.user_id().unwrap());
|
||||
assert_eq!(notification.joined_members_count, 1);
|
||||
assert_eq!(notification.is_room_encrypted, None);
|
||||
assert!(notification.is_direct_message_room);
|
||||
|
||||
assert_matches!(notification.event, NotificationEvent::Invite(observed_invite) => {
|
||||
assert_eq!(observed_invite.content.membership, MembershipState::Invite);
|
||||
});
|
||||
|
||||
assert_eq!(notification.sender_display_name.as_deref(), Some(ALICE_NAME));
|
||||
|
||||
// In theory, the room name ought to be ROOM_NAME here, but the sliding sync
|
||||
// proxy returns the other person's name as the room's name (as of
|
||||
// 2023-08-04).
|
||||
assert!(notification.room_display_name != ROOM_NAME);
|
||||
assert_eq!(notification.room_display_name, ALICE_NAME);
|
||||
|
||||
// Then with /context.
|
||||
let notification_client = NotificationClient::builder(bob.clone()).await.unwrap().build();
|
||||
let notification =
|
||||
notification_client.get_notification_with_context(&room_id, &event_id).await;
|
||||
// We aren't authorized to inspect events from rooms we were not invited to.
|
||||
assert!(matches!(notification.unwrap_err(), Error::SdkError(matrix_sdk::Error::Http(..))));
|
||||
} else {
|
||||
warn!("Couldn't get the invite event.");
|
||||
}
|
||||
|
||||
// Bob accepts the invite, joins the room.
|
||||
{
|
||||
let room = bob.get_room(&room_id).expect("bob doesn't know about the room");
|
||||
ensure!(
|
||||
room.state() == RoomState::Invited,
|
||||
"The room alice invited bob in isn't an invite: {room:?}"
|
||||
);
|
||||
let details = room.invite_details().await?;
|
||||
let sender = details.inviter.expect("invite details doesn't have inviter");
|
||||
assert_eq!(sender.user_id(), alice.user_id().expect("alice has a user_id"));
|
||||
}
|
||||
|
||||
// Bob joins the room.
|
||||
bob.get_room(alice_room.room_id()).unwrap().join().await?;
|
||||
|
||||
// Now Alice sends a message to Bob.
|
||||
alice_room.send(RoomMessageEventContent::text_plain("Hello world!"), None).await?;
|
||||
|
||||
// In this sync, bob receives the message from Alice.
|
||||
let bob_response = bob.sync_once(SyncSettings::default().token(sync_token)).await?;
|
||||
|
||||
let mut joined_rooms = bob_response.rooms.join.into_iter();
|
||||
let (_, bob_room) = joined_rooms.next().expect("must have joined one room");
|
||||
assert!(joined_rooms.next().is_none(), "no more joined rooms: {joined_rooms:#?}");
|
||||
|
||||
let event_id = bob_room
|
||||
.timeline
|
||||
.events
|
||||
.iter()
|
||||
.find_map(|event| {
|
||||
let event = event.event.deserialize().ok()?;
|
||||
if event.event_type() == TimelineEventType::RoomMessage {
|
||||
Some(event.event_id().to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.expect("missing message from alice in bob's client");
|
||||
|
||||
// Get the notification for the given message.
|
||||
let check_notification = |is_sliding_sync: bool, notification: NotificationItem| {
|
||||
warn!(
|
||||
"{}: checking message notification",
|
||||
if is_sliding_sync { "sliding sync" } else { "/context query" }
|
||||
);
|
||||
|
||||
assert_eq!(notification.event.sender(), alice.user_id().unwrap());
|
||||
|
||||
if is_sliding_sync {
|
||||
assert_eq!(notification.joined_members_count, 2);
|
||||
} else {
|
||||
// This can't be computed for /context, because we only get a single request,
|
||||
// and not a full sync response that would contain a room summary.
|
||||
warn!("joined member counts: {}", notification.joined_members_count);
|
||||
}
|
||||
|
||||
assert_eq!(notification.is_room_encrypted, Some(false));
|
||||
assert!(notification.is_direct_message_room);
|
||||
|
||||
assert_matches!(
|
||||
notification.event,
|
||||
NotificationEvent::Timeline(
|
||||
matrix_sdk::ruma::events::AnySyncTimelineEvent::MessageLike(
|
||||
matrix_sdk::ruma::events::AnySyncMessageLikeEvent::RoomMessage(
|
||||
SyncMessageLikeEvent::Original(event)
|
||||
)
|
||||
)
|
||||
) => {
|
||||
assert_matches!(event.content.msgtype,
|
||||
matrix_sdk::ruma::events::room::message::MessageType::Text(text) => {
|
||||
assert_eq!(text.body, "Hello world!");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(notification.sender_display_name.as_deref(), Some(ALICE_NAME));
|
||||
assert_eq!(notification.room_display_name, ROOM_NAME);
|
||||
};
|
||||
|
||||
let notification_client = NotificationClient::builder(bob.clone()).await.unwrap().build();
|
||||
let notification = notification_client
|
||||
.get_notification_with_sliding_sync(&room_id, &event_id)
|
||||
.await?
|
||||
.expect("missing notification for the message");
|
||||
check_notification(true, notification);
|
||||
|
||||
let notification_client = NotificationClient::builder(bob.clone()).await.unwrap().build();
|
||||
let notification = notification_client
|
||||
.get_notification_with_context(&room_id, &event_id)
|
||||
.await?
|
||||
.expect("missing notification for the message");
|
||||
check_notification(false, notification);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user