appservice: Improve test coverage
This commit is contained in:
parent
38512d6a54
commit
8d061447d6
9 changed files with 320 additions and 208 deletions
|
@ -482,7 +482,7 @@ impl RequestConfig {
|
|||
/// Default is only sending authorization if it is required
|
||||
#[cfg(any(feature = "require_auth_for_profile_requests", feature = "appservice"))]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(any(require_auth_for_profile_requests, appservice))))]
|
||||
pub fn force_auth(mut self) -> Self {
|
||||
pub(crate) fn force_auth(mut self) -> Self {
|
||||
self.force_auth = true;
|
||||
self
|
||||
}
|
||||
|
@ -1367,7 +1367,7 @@ impl Client {
|
|||
let config = None;
|
||||
|
||||
#[cfg(feature = "appservice")]
|
||||
let config = Some(RequestConfig::new().force_auth());
|
||||
let config = Some(self.http_client.request_config.force_auth());
|
||||
|
||||
let request = registration.into();
|
||||
self.send(request, config).await
|
||||
|
|
|
@ -27,7 +27,7 @@ serde_yaml = "0.8"
|
|||
thiserror = "1.0"
|
||||
tracing = "0.1"
|
||||
url = "2"
|
||||
warp = { version = "0.3", optional = true }
|
||||
warp = { version = "0.3", optional = true, default-features = false, features = ["multipart", "websocket"] }
|
||||
|
||||
matrix-sdk = { version = "0.2", path = "../matrix_sdk", default-features = false, features = ["appservice", "native-tls"] }
|
||||
|
||||
|
@ -41,8 +41,8 @@ mockito = "0.30"
|
|||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||
tracing-subscriber = "0.2"
|
||||
|
||||
matrix-sdk-test = { version = "0.2", path = "../matrix_sdk_test" }
|
||||
matrix-sdk-test = { version = "0.2", path = "../matrix_sdk_test", features = ["appservice"] }
|
||||
|
||||
[[example]]
|
||||
name = "actix_autojoin"
|
||||
name = "autojoin"
|
||||
required-features = ["actix"]
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
use std::{convert::TryFrom, env};
|
||||
|
||||
use actix_web::{App, HttpServer};
|
||||
use matrix_sdk::{
|
||||
async_trait,
|
||||
events::{
|
||||
room::member::{MemberEventContent, MembershipState},
|
||||
SyncStateEvent,
|
||||
},
|
||||
identifiers::UserId,
|
||||
room::Room,
|
||||
EventHandler,
|
||||
};
|
||||
use matrix_sdk_appservice::{Appservice, AppserviceRegistration};
|
||||
|
||||
struct AppserviceEventHandler {
|
||||
appservice: Appservice,
|
||||
}
|
||||
|
||||
impl AppserviceEventHandler {
|
||||
pub fn new(appservice: Appservice) -> Self {
|
||||
Self { appservice }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for AppserviceEventHandler {
|
||||
async fn on_room_member(&self, room: Room, event: &SyncStateEvent<MemberEventContent>) {
|
||||
if !self.appservice.user_id_is_in_namespace(&event.state_key).unwrap() {
|
||||
dbg!("not an appservice user");
|
||||
return;
|
||||
}
|
||||
|
||||
if let MembershipState::Invite = event.content.membership {
|
||||
let user_id = UserId::try_from(event.state_key.clone()).unwrap();
|
||||
|
||||
let appservice = self.appservice.clone();
|
||||
appservice.register_virtual_user(user_id.localpart()).await.unwrap();
|
||||
|
||||
let client = appservice.virtual_user(user_id.localpart()).await.unwrap();
|
||||
|
||||
client.join_room_by_id(room.room_id()).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
pub async fn main() -> std::io::Result<()> {
|
||||
env::set_var("RUST_LOG", "actix_web=debug,actix_server=info,matrix_sdk=debug");
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let homeserver_url = "http://localhost:8008";
|
||||
let server_name = "localhost";
|
||||
let registration =
|
||||
AppserviceRegistration::try_from_yaml_file("./tests/registration.yaml").unwrap();
|
||||
|
||||
let mut appservice = Appservice::new(homeserver_url, server_name, registration).await.unwrap();
|
||||
|
||||
let event_handler = AppserviceEventHandler::new(appservice.clone());
|
||||
|
||||
appservice.set_event_handler(Box::new(event_handler)).await.unwrap();
|
||||
|
||||
HttpServer::new(move || App::new().service(appservice.actix_service()))
|
||||
.bind(("0.0.0.0", 8090))?
|
||||
.run()
|
||||
.await
|
||||
}
|
75
matrix_sdk_appservice/examples/autojoin.rs
Normal file
75
matrix_sdk_appservice/examples/autojoin.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use std::{convert::TryFrom, env};
|
||||
|
||||
use matrix_sdk_appservice::{
|
||||
sdk::{
|
||||
async_trait,
|
||||
events::{
|
||||
room::member::{MemberEventContent, MembershipState},
|
||||
SyncStateEvent,
|
||||
},
|
||||
identifiers::UserId,
|
||||
room::Room,
|
||||
EventHandler,
|
||||
},
|
||||
Appservice, AppserviceRegistration,
|
||||
};
|
||||
use tracing::{error, trace};
|
||||
|
||||
struct AppserviceEventHandler {
|
||||
appservice: Appservice,
|
||||
}
|
||||
|
||||
impl AppserviceEventHandler {
|
||||
pub fn new(appservice: Appservice) -> Self {
|
||||
Self { appservice }
|
||||
}
|
||||
|
||||
pub async fn handle_room_member(
|
||||
&self,
|
||||
room: Room,
|
||||
event: &SyncStateEvent<MemberEventContent>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !self.appservice.user_id_is_in_namespace(&event.state_key)? {
|
||||
trace!("not an appservice user: {}", event.state_key);
|
||||
} else if let MembershipState::Invite = event.content.membership {
|
||||
let user_id = UserId::try_from(event.state_key.clone())?;
|
||||
|
||||
let appservice = self.appservice.clone();
|
||||
appservice.register_virtual_user(user_id.localpart()).await?;
|
||||
|
||||
let client = appservice.virtual_user_client(user_id.localpart()).await?;
|
||||
|
||||
client.join_room_by_id(room.room_id()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for AppserviceEventHandler {
|
||||
async fn on_room_member(&self, room: Room, event: &SyncStateEvent<MemberEventContent>) {
|
||||
match self.handle_room_member(room, event).await {
|
||||
Ok(_) => (),
|
||||
Err(error) => error!("{:?}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env::set_var("RUST_LOG", "matrix_sdk=debug,matrix_sdk_appservice=debug");
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let homeserver_url = "http://localhost:8008";
|
||||
let server_name = "localhost";
|
||||
let registration = AppserviceRegistration::try_from_yaml_file("./tests/registration.yaml")?;
|
||||
|
||||
let mut appservice = Appservice::new(homeserver_url, server_name, registration).await?;
|
||||
appservice.set_event_handler(Box::new(AppserviceEventHandler::new(appservice.clone()))).await?;
|
||||
|
||||
let (host, port) = appservice.registration().get_host_and_port()?;
|
||||
appservice.run(host, port).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -88,28 +88,22 @@ use std::{
|
|||
use dashmap::DashMap;
|
||||
pub use error::Error;
|
||||
use http::{uri::PathAndQuery, Uri};
|
||||
pub use matrix_sdk as sdk;
|
||||
use matrix_sdk::{
|
||||
reqwest::Url, Bytes, Client, ClientConfig, EventHandler, HttpError, RequestConfig, Session,
|
||||
};
|
||||
pub use matrix_sdk;
|
||||
use matrix_sdk::{reqwest::Url, Bytes, Client, ClientConfig, EventHandler, HttpError, Session};
|
||||
use regex::Regex;
|
||||
#[doc(inline)]
|
||||
pub use ruma::api::appservice as api;
|
||||
pub use ruma::api::{appservice as api, appservice::Registration};
|
||||
use ruma::{
|
||||
api::{
|
||||
appservice::Registration,
|
||||
client::{
|
||||
error::ErrorKind,
|
||||
r0::{
|
||||
account::register::{LoginType, Request as RegistrationRequest},
|
||||
uiaa::UiaaResponse,
|
||||
},
|
||||
r0::{account::register, uiaa::UiaaResponse},
|
||||
},
|
||||
error::{FromHttpResponseError, ServerError},
|
||||
},
|
||||
assign, identifiers, DeviceId, ServerNameBox, UserId,
|
||||
};
|
||||
use tracing::warn;
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
mod actix;
|
||||
|
@ -255,8 +249,8 @@ impl Appservice {
|
|||
|
||||
let appservice = Appservice { homeserver_url, server_name, registration, clients };
|
||||
|
||||
// we cache the [`MainUser`] by default
|
||||
appservice.virtual_user_with_config(sender_localpart, client_config).await?;
|
||||
// we create and cache the [`MainUser`] by default
|
||||
appservice.create_and_cache_client(&sender_localpart, client_config).await?;
|
||||
|
||||
Ok(appservice)
|
||||
}
|
||||
|
@ -272,24 +266,29 @@ impl Appservice {
|
|||
/// by calling this method again or by calling [`Self::get_cached_client()`]
|
||||
/// which is non-async convenience wrapper.
|
||||
///
|
||||
/// Note that if you want to do actions like joining rooms with a virtual
|
||||
/// user it needs to be registered first. `Self::register_virtual_user()`
|
||||
/// can be used for that purpose.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `localpart` - The localpart of the user we want assert our identity to
|
||||
///
|
||||
/// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration
|
||||
/// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion
|
||||
pub async fn virtual_user(&self, localpart: impl AsRef<str>) -> Result<Client> {
|
||||
let client = self.virtual_user_with_config(localpart, ClientConfig::default()).await?;
|
||||
pub async fn virtual_user_client(&self, localpart: impl AsRef<str>) -> Result<Client> {
|
||||
let client =
|
||||
self.virtual_user_client_with_config(localpart, ClientConfig::default()).await?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// Same as [`Self::virtual_user()`] but with the ability to pass in a
|
||||
/// [`ClientConfig`]
|
||||
/// Same as [`Self::virtual_user_client()`] but with the ability to pass in
|
||||
/// a [`ClientConfig`]
|
||||
///
|
||||
/// Since this method is a singleton follow-up calls with different
|
||||
/// [`ClientConfig`]s will be ignored.
|
||||
pub async fn virtual_user_with_config(
|
||||
pub async fn virtual_user_client_with_config(
|
||||
&self,
|
||||
localpart: impl AsRef<str>,
|
||||
config: ClientConfig,
|
||||
|
@ -300,38 +299,49 @@ impl Appservice {
|
|||
let client = if let Some(client) = self.clients.get(localpart) {
|
||||
client.clone()
|
||||
} else {
|
||||
let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?;
|
||||
|
||||
// The `as_token` in the `Session` maps to the [`MainUser`]
|
||||
// (`sender_localpart`) by default, so we don't need to assert identity
|
||||
// in that case
|
||||
if localpart != self.registration.sender_localpart {
|
||||
config.get_request_config().assert_identity();
|
||||
}
|
||||
|
||||
let client = Client::new_with_config(self.homeserver_url.clone(), config)?;
|
||||
|
||||
let session = Session {
|
||||
access_token: self.registration.as_token.clone(),
|
||||
user_id: user_id.clone(),
|
||||
// TODO: expose & proper E2EE
|
||||
device_id: DeviceId::new(),
|
||||
};
|
||||
|
||||
client.restore_login(session).await?;
|
||||
self.clients.insert(localpart.to_owned(), client.clone());
|
||||
|
||||
client
|
||||
self.create_and_cache_client(localpart, config).await?
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
async fn create_and_cache_client(
|
||||
&self,
|
||||
localpart: &str,
|
||||
config: ClientConfig,
|
||||
) -> Result<Client> {
|
||||
let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?;
|
||||
|
||||
// The `as_token` in the `Session` maps to the [`MainUser`]
|
||||
// (`sender_localpart`) by default, so we don't need to assert identity
|
||||
// in that case
|
||||
let config = if localpart != self.registration.sender_localpart {
|
||||
let request_config = config.get_request_config().assert_identity();
|
||||
config.request_config(request_config)
|
||||
} else {
|
||||
config
|
||||
};
|
||||
|
||||
let client = Client::new_with_config(self.homeserver_url.clone(), config)?;
|
||||
|
||||
let session = Session {
|
||||
access_token: self.registration.as_token.clone(),
|
||||
user_id: user_id.clone(),
|
||||
// TODO: expose & proper E2EE
|
||||
device_id: DeviceId::new(),
|
||||
};
|
||||
|
||||
client.restore_login(session).await?;
|
||||
self.clients.insert(localpart.to_owned(), client.clone());
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// Get cached [`Client`]
|
||||
///
|
||||
/// Will return the client for the given `localpart` if previously
|
||||
/// constructed with [`Self::virtual_user()`] or
|
||||
/// [`Self::virtual_user_with_config()`].
|
||||
/// constructed with [`Self::virtual_user_client()`] or
|
||||
/// [`Self::virtual_user_client_with_config()`].
|
||||
///
|
||||
/// If no `localpart` is given it assumes the [`MainUser`]'s `localpart`. If
|
||||
/// no client for `localpart` is found it will return an Error.
|
||||
|
@ -354,7 +364,7 @@ impl Appservice {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a virtual user by sending a [`RegistrationRequest`] to the
|
||||
/// Register a virtual user by sending a [`register::Request`] to the
|
||||
/// homeserver
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -362,17 +372,13 @@ impl Appservice {
|
|||
/// * `localpart` - The localpart of the user to register. Must be covered
|
||||
/// by the namespaces in the [`Registration`] in order to succeed.
|
||||
pub async fn register_virtual_user(&self, localpart: impl AsRef<str>) -> Result<()> {
|
||||
let request = assign!(RegistrationRequest::new(), {
|
||||
let request = assign!(register::Request::new(), {
|
||||
username: Some(localpart.as_ref()),
|
||||
login_type: Some(&LoginType::ApplicationService),
|
||||
login_type: Some(®ister::LoginType::ApplicationService),
|
||||
});
|
||||
|
||||
let client = self.get_cached_client(None)?;
|
||||
|
||||
// TODO: use `client.register()` instead
|
||||
// blocked by: https://github.com/seanmonstar/warp/pull/861
|
||||
let config = Some(RequestConfig::new().force_auth());
|
||||
match client.send(request, config).await {
|
||||
match client.register(request).await {
|
||||
Ok(_) => (),
|
||||
Err(error) => match error {
|
||||
matrix_sdk::Error::Http(HttpError::UiaaError(FromHttpResponseError::Http(
|
||||
|
@ -441,6 +447,10 @@ impl Appservice {
|
|||
/// This is a blocking call that tries to listen on the provided host and
|
||||
/// port
|
||||
pub async fn run(&self, host: impl Into<String>, port: impl Into<u16>) -> Result<()> {
|
||||
let host = host.into();
|
||||
let port = port.into();
|
||||
info!("Starting Appservice on {}:{}", &host, &port);
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
{
|
||||
actix::run_server(self.clone(), host, port).await?;
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
use std::env;
|
||||
use std::{
|
||||
env,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
use actix_web::{test as actix_test, App as ActixApp};
|
||||
use matrix_sdk::{
|
||||
api_appservice,
|
||||
api_appservice::Registration,
|
||||
async_trait,
|
||||
events::{room::member::MemberEventContent, AnyRoomEvent, AnyStateEvent, SyncStateEvent},
|
||||
events::{room::member::MemberEventContent, SyncStateEvent},
|
||||
room::Room,
|
||||
EventHandler, Raw,
|
||||
EventHandler,
|
||||
};
|
||||
use matrix_sdk_appservice::*;
|
||||
use matrix_sdk_test::async_test;
|
||||
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, EventsJson};
|
||||
use sdk::{ClientConfig, RequestConfig};
|
||||
use serde_json::json;
|
||||
#[cfg(feature = "warp")]
|
||||
use warp::Reply;
|
||||
|
@ -35,56 +38,63 @@ async fn appservice(registration: Option<Registration>) -> Result<Appservice> {
|
|||
let homeserver_url = mockito::server_url();
|
||||
let server_name = "localhost";
|
||||
|
||||
Ok(Appservice::new(homeserver_url.as_ref(), server_name, registration).await?)
|
||||
let client_config =
|
||||
ClientConfig::default().request_config(RequestConfig::default().disable_retry());
|
||||
|
||||
Ok(Appservice::new_with_config(
|
||||
homeserver_url.as_ref(),
|
||||
server_name,
|
||||
registration,
|
||||
client_config,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
fn member_json() -> serde_json::Value {
|
||||
json!({
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "example",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "$151800140517rfvjc:localhost",
|
||||
"membership": "join",
|
||||
"origin_server_ts": 151800140,
|
||||
"room_id": "!ahpSDaDUPCCqktjUEF:localhost",
|
||||
"sender": "@example:localhost",
|
||||
"state_key": "@example:localhost",
|
||||
"type": "m.room.member",
|
||||
"prev_content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "example",
|
||||
"membership": "invite"
|
||||
},
|
||||
"unsigned": {
|
||||
"age": 297036,
|
||||
"replaces_state": "$151800111315tsynI:localhost"
|
||||
}
|
||||
})
|
||||
#[async_test]
|
||||
async fn test_register_virtual_user() -> Result<()> {
|
||||
let appservice = appservice(None).await?;
|
||||
|
||||
let localpart = "someone";
|
||||
let _mock = mockito::mock("POST", "/_matrix/client/r0/register")
|
||||
.match_query(mockito::Matcher::Missing)
|
||||
.match_header(
|
||||
"authorization",
|
||||
mockito::Matcher::Exact(format!("Bearer {}", appservice.registration().as_token)),
|
||||
)
|
||||
.match_body(mockito::Matcher::Json(json!({
|
||||
"username": localpart.to_owned(),
|
||||
"type": "m.login.application_service"
|
||||
})))
|
||||
.with_body(format!(
|
||||
r#"{{
|
||||
"access_token": "abc123",
|
||||
"device_id": "GHTYAJCE",
|
||||
"user_id": "@{localpart}:localhost"
|
||||
}}"#,
|
||||
localpart = localpart
|
||||
))
|
||||
.create();
|
||||
|
||||
appservice.register_virtual_user(localpart).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_put_transaction() -> Result<()> {
|
||||
let appservice = appservice(None).await?;
|
||||
|
||||
let transactions = r#"{
|
||||
"events": [
|
||||
{
|
||||
"content": {},
|
||||
"type": "m.dummy"
|
||||
}
|
||||
]
|
||||
}"#;
|
||||
|
||||
let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token";
|
||||
let transactions: serde_json::Value = serde_json::from_str(transactions)?;
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
transaction_builder.add_room_event(EventsJson::Member);
|
||||
let transaction = transaction_builder.build_json_transaction();
|
||||
|
||||
let appservice = appservice(None).await?;
|
||||
|
||||
#[cfg(feature = "warp")]
|
||||
let status = warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transactions)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -96,7 +106,7 @@ async fn test_put_transaction() -> Result<()> {
|
|||
let app =
|
||||
actix_test::init_service(ActixApp::new().service(appservice.actix_service())).await;
|
||||
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transactions).to_request();
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||
|
||||
actix_test::call_service(&app, req).await.status()
|
||||
};
|
||||
|
@ -170,25 +180,19 @@ async fn test_get_room() -> Result<()> {
|
|||
|
||||
#[async_test]
|
||||
async fn test_invalid_access_token() -> Result<()> {
|
||||
let appservice = appservice(None).await?;
|
||||
|
||||
let transactions = r#"{
|
||||
"events": [
|
||||
{
|
||||
"content": {},
|
||||
"type": "m.dummy"
|
||||
}
|
||||
]
|
||||
}"#;
|
||||
|
||||
let transactions: serde_json::Value = serde_json::from_str(transactions).unwrap();
|
||||
let uri = "/_matrix/app/v1/transactions/1?access_token=invalid_token";
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
let transaction =
|
||||
transaction_builder.add_room_event(EventsJson::Member).build_json_transaction();
|
||||
|
||||
let appservice = appservice(None).await?;
|
||||
|
||||
#[cfg(feature = "warp")]
|
||||
let status = warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transactions)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -200,7 +204,7 @@ async fn test_invalid_access_token() -> Result<()> {
|
|||
let app =
|
||||
actix_test::init_service(ActixApp::new().service(appservice.actix_service())).await;
|
||||
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transactions).to_request();
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||
|
||||
actix_test::call_service(&app, req).await.status()
|
||||
};
|
||||
|
@ -212,27 +216,20 @@ async fn test_invalid_access_token() -> Result<()> {
|
|||
|
||||
#[async_test]
|
||||
async fn test_no_access_token() -> Result<()> {
|
||||
let appservice = appservice(None).await?;
|
||||
|
||||
let transactions = r#"{
|
||||
"events": [
|
||||
{
|
||||
"content": {},
|
||||
"type": "m.dummy"
|
||||
}
|
||||
]
|
||||
}"#;
|
||||
|
||||
let transactions: serde_json::Value = serde_json::from_str(transactions).unwrap();
|
||||
|
||||
let uri = "/_matrix/app/v1/transactions/1";
|
||||
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
transaction_builder.add_room_event(EventsJson::Member);
|
||||
let transaction = transaction_builder.build_json_transaction();
|
||||
|
||||
let appservice = appservice(None).await?;
|
||||
|
||||
#[cfg(feature = "warp")]
|
||||
{
|
||||
let status = warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transactions)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -247,7 +244,7 @@ async fn test_no_access_token() -> Result<()> {
|
|||
let app =
|
||||
actix_test::init_service(ActixApp::new().service(appservice.actix_service())).await;
|
||||
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transactions).to_request();
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||
|
||||
let resp = actix_test::call_service(&app, req).await;
|
||||
|
||||
|
@ -263,33 +260,56 @@ async fn test_no_access_token() -> Result<()> {
|
|||
async fn test_event_handler() -> Result<()> {
|
||||
let mut appservice = appservice(None).await?;
|
||||
|
||||
struct Example {}
|
||||
#[derive(Clone)]
|
||||
struct Example {
|
||||
pub on_state_member: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl Example {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
#[allow(clippy::mutex::mutex_atomic)]
|
||||
Self { on_state_member: Arc::new(Mutex::new(false)) }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for Example {
|
||||
async fn on_state_member(&self, room: Room, event: &SyncStateEvent<MemberEventContent>) {
|
||||
dbg!(room, event);
|
||||
async fn on_room_member(&self, _: Room, _: &SyncStateEvent<MemberEventContent>) {
|
||||
let on_state_member = self.on_state_member.clone();
|
||||
*on_state_member.lock().unwrap() = true;
|
||||
}
|
||||
}
|
||||
|
||||
appservice.set_event_handler(Box::new(Example::new())).await?;
|
||||
let example = Example::new();
|
||||
appservice.set_event_handler(Box::new(example.clone())).await?;
|
||||
|
||||
let event = serde_json::from_value::<AnyStateEvent>(member_json()).unwrap();
|
||||
let event: Raw<AnyRoomEvent> = AnyRoomEvent::State(event).into();
|
||||
let events = vec![event];
|
||||
let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token";
|
||||
|
||||
let incoming = api_appservice::event::push_events::v1::IncomingRequest::new(
|
||||
"any_txn_id".to_owned(),
|
||||
events,
|
||||
);
|
||||
let mut transaction_builder = TransactionBuilder::new();
|
||||
transaction_builder.add_room_event(EventsJson::Member);
|
||||
let transaction = transaction_builder.build_json_transaction();
|
||||
|
||||
appservice.get_cached_client(None)?.receive_transaction(incoming).await?;
|
||||
#[cfg(feature = "warp")]
|
||||
warp::test::request()
|
||||
.method("PUT")
|
||||
.path(uri)
|
||||
.json(&transaction)
|
||||
.filter(&appservice.warp_filter())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
let status = {
|
||||
let app =
|
||||
actix_test::init_service(ActixApp::new().service(appservice.actix_service())).await;
|
||||
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||
|
||||
actix_test::call_service(&app, req).await;
|
||||
};
|
||||
|
||||
let on_room_member_called = *example.on_state_member.lock().unwrap();
|
||||
assert!(on_room_member_called);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -10,6 +10,9 @@ readme = "README.md"
|
|||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
version = "0.2.0"
|
||||
|
||||
[features]
|
||||
appservice = []
|
||||
|
||||
[dependencies]
|
||||
http = "0.2.3"
|
||||
lazy_static = "1.4.0"
|
||||
|
|
69
matrix_sdk_test/src/appservice.rs
Normal file
69
matrix_sdk_test/src/appservice.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use ruma::{events::AnyRoomEvent, identifiers::room_id};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{test_json, EventsJson};
|
||||
|
||||
/// Clones the given [`Value`] and adds a `room_id` to it
|
||||
///
|
||||
/// Adding the `room_id` conditionally with `cfg` directly to the lazy_static
|
||||
/// test_json values is blocked by "experimental attributes on expressions, see
|
||||
/// issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information"
|
||||
pub fn value_with_room_id(value: &Value) -> Value {
|
||||
let mut val = value.clone();
|
||||
let room_id =
|
||||
Value::try_from(room_id!("!SVkFJHzfwvuaIEawgC:localhost").to_string()).expect("room_id");
|
||||
val.as_object_mut().expect("mutable test_json").insert("room_id".to_owned(), room_id);
|
||||
|
||||
val
|
||||
}
|
||||
|
||||
/// The `TransactionBuilder` struct can be used to easily generate valid
|
||||
/// incoming appservice transactions in json value format for testing.
|
||||
///
|
||||
/// Usage is similar to [`super::EventBuilder`]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TransactionBuilder {
|
||||
events: Vec<AnyRoomEvent>,
|
||||
}
|
||||
|
||||
impl TransactionBuilder {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Add a room event.
|
||||
pub fn add_room_event(&mut self, json: EventsJson) -> &mut Self {
|
||||
let val: &Value = match json {
|
||||
EventsJson::Member => &test_json::MEMBER,
|
||||
EventsJson::MemberNameChange => &test_json::MEMBER_NAME_CHANGE,
|
||||
EventsJson::PowerLevels => &test_json::POWER_LEVELS,
|
||||
_ => panic!("unknown event json {:?}", json),
|
||||
};
|
||||
|
||||
let val = value_with_room_id(val);
|
||||
|
||||
let event = serde_json::from_value::<AnyRoomEvent>(val).unwrap();
|
||||
|
||||
self.events.push(event);
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the transaction
|
||||
#[cfg(feature = "appservice")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(appservice)))]
|
||||
pub fn build_json_transaction(&self) -> Value {
|
||||
let body = serde_json::json! {
|
||||
{
|
||||
"events": self.events
|
||||
}
|
||||
};
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.events.clear();
|
||||
}
|
||||
}
|
|
@ -12,6 +12,8 @@ use ruma::{
|
|||
};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
#[cfg(feature = "appservice")]
|
||||
pub mod appservice;
|
||||
pub mod test_json;
|
||||
|
||||
/// Embedded event files
|
||||
|
|
Loading…
Reference in a new issue