BuildKitClient CLI wrapper for buildctl. Docker compose stack (9 services) for integration testing. Comprehensive test suite: wiremock tests for Matrix/La Suite/S3/client, integration tests for Kratos/Hydra/Gitea/OpenSearch/Prometheus/Loki/ Grafana/LiveKit. Bump: sunbeam-sdk v0.12.0
903 lines
33 KiB
Rust
903 lines
33 KiB
Rust
#![cfg(feature = "integration")]
|
|
use sunbeam_sdk::client::{AuthMethod, ServiceClient};
|
|
use sunbeam_sdk::matrix::MatrixClient;
|
|
use sunbeam_sdk::matrix::types::*;
|
|
use wiremock::{MockServer, Mock, ResponseTemplate};
|
|
use wiremock::matchers::{method, path, path_regex};
|
|
|
|
fn client(uri: &str) -> MatrixClient {
|
|
MatrixClient::from_parts(uri.to_string(), AuthMethod::Bearer("test-token".into()))
|
|
}
|
|
|
|
fn ok_json(body: serde_json::Value) -> ResponseTemplate {
|
|
ResponseTemplate::new(200).set_body_json(body)
|
|
}
|
|
|
|
fn ok_empty() -> ResponseTemplate {
|
|
ResponseTemplate::new(200).set_body_json(serde_json::json!({}))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ServiceClient trait + connect / set_token
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn service_client_trait() {
|
|
let server = MockServer::start().await;
|
|
let mut c = client(&server.uri());
|
|
assert_eq!(c.service_name(), "matrix");
|
|
assert_eq!(c.base_url(), server.uri());
|
|
|
|
c.set_token("new-tok");
|
|
// just exercises set_token; nothing to assert beyond no panic
|
|
|
|
let c2 = MatrixClient::connect("example.com");
|
|
assert_eq!(c2.base_url(), "https://matrix.example.com/_matrix");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Auth endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn auth_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// list_login_types
|
|
Mock::given(method("GET")).and(path("/client/v3/login"))
|
|
.respond_with(ok_json(serde_json::json!({"flows": []})))
|
|
.mount(&server).await;
|
|
let r = c.list_login_types().await.unwrap();
|
|
assert!(r.flows.is_empty());
|
|
|
|
// login
|
|
Mock::given(method("POST")).and(path("/client/v3/login"))
|
|
.respond_with(ok_json(serde_json::json!({
|
|
"user_id": "@u:localhost",
|
|
"access_token": "tok",
|
|
"device_id": "D1"
|
|
})))
|
|
.mount(&server).await;
|
|
let body = LoginRequest {
|
|
login_type: "m.login.password".into(),
|
|
identifier: None, password: Some("pw".into()), token: None,
|
|
device_id: None, initial_device_display_name: None, refresh_token: None,
|
|
};
|
|
let r = c.login(&body).await.unwrap();
|
|
assert_eq!(r.user_id, "@u:localhost");
|
|
|
|
// refresh
|
|
Mock::given(method("POST")).and(path("/client/v3/refresh"))
|
|
.respond_with(ok_json(serde_json::json!({
|
|
"access_token": "new-tok"
|
|
})))
|
|
.mount(&server).await;
|
|
let r = c.refresh(&RefreshRequest { refresh_token: "rt".into() }).await.unwrap();
|
|
assert_eq!(r.access_token, "new-tok");
|
|
|
|
// logout
|
|
Mock::given(method("POST")).and(path("/client/v3/logout"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.logout().await.unwrap();
|
|
|
|
// logout_all
|
|
Mock::given(method("POST")).and(path("/client/v3/logout/all"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.logout_all().await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Account endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn account_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// register
|
|
Mock::given(method("POST")).and(path("/client/v3/register"))
|
|
.respond_with(ok_json(serde_json::json!({"user_id": "@new:localhost"})))
|
|
.mount(&server).await;
|
|
let body = RegisterRequest {
|
|
username: Some("new".into()), password: Some("pw".into()),
|
|
device_id: None, initial_device_display_name: None,
|
|
inhibit_login: None, refresh_token: None, auth: None, kind: None,
|
|
};
|
|
let r = c.register(&body).await.unwrap();
|
|
assert_eq!(r.user_id, "@new:localhost");
|
|
|
|
// whoami
|
|
Mock::given(method("GET")).and(path("/client/v3/account/whoami"))
|
|
.respond_with(ok_json(serde_json::json!({"user_id": "@me:localhost"})))
|
|
.mount(&server).await;
|
|
let r = c.whoami().await.unwrap();
|
|
assert_eq!(r.user_id, "@me:localhost");
|
|
|
|
// list_3pids
|
|
Mock::given(method("GET")).and(path("/client/v3/account/3pid"))
|
|
.respond_with(ok_json(serde_json::json!({"threepids": []})))
|
|
.mount(&server).await;
|
|
let r = c.list_3pids().await.unwrap();
|
|
assert!(r.threepids.is_empty());
|
|
|
|
// add_3pid
|
|
Mock::given(method("POST")).and(path("/client/v3/account/3pid/add"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.add_3pid(&Add3pidRequest { client_secret: None, sid: None, auth: None }).await.unwrap();
|
|
|
|
// delete_3pid
|
|
Mock::given(method("POST")).and(path("/client/v3/account/3pid/delete"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.delete_3pid(&Delete3pidRequest {
|
|
medium: "email".into(), address: "a@b.c".into(), id_server: None,
|
|
}).await.unwrap();
|
|
|
|
// change_password
|
|
Mock::given(method("POST")).and(path("/client/v3/account/password"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.change_password(&ChangePasswordRequest {
|
|
new_password: "new-pw".into(), logout_devices: None, auth: None,
|
|
}).await.unwrap();
|
|
|
|
// deactivate
|
|
Mock::given(method("POST")).and(path("/client/v3/account/deactivate"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.deactivate(&DeactivateRequest { auth: None, id_server: None, erase: None }).await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Room endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn room_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// create_room
|
|
Mock::given(method("POST")).and(path("/client/v3/createRoom"))
|
|
.respond_with(ok_json(serde_json::json!({"room_id": "!r:localhost"})))
|
|
.mount(&server).await;
|
|
let body = CreateRoomRequest {
|
|
name: Some("test".into()), topic: None, room_alias_name: None,
|
|
visibility: None, preset: None, invite: None, is_direct: None,
|
|
creation_content: None, initial_state: None, power_level_content_override: None,
|
|
};
|
|
let r = c.create_room(&body).await.unwrap();
|
|
assert_eq!(r.room_id, "!r:localhost");
|
|
|
|
// list_public_rooms — no params
|
|
Mock::given(method("GET")).and(path("/client/v3/publicRooms"))
|
|
.respond_with(ok_json(serde_json::json!({"chunk": []})))
|
|
.mount(&server).await;
|
|
let r = c.list_public_rooms(None, None).await.unwrap();
|
|
assert!(r.chunk.is_empty());
|
|
|
|
// list_public_rooms — with limit + since (exercises query-string branch)
|
|
let r = c.list_public_rooms(Some(10), Some("tok")).await.unwrap();
|
|
assert!(r.chunk.is_empty());
|
|
|
|
// search_public_rooms
|
|
Mock::given(method("POST")).and(path("/client/v3/publicRooms"))
|
|
.respond_with(ok_json(serde_json::json!({"chunk": []})))
|
|
.mount(&server).await;
|
|
let body = SearchPublicRoomsRequest {
|
|
limit: None, since: None, filter: None,
|
|
include_all_networks: None, third_party_instance_id: None,
|
|
};
|
|
let r = c.search_public_rooms(&body).await.unwrap();
|
|
assert!(r.chunk.is_empty());
|
|
|
|
// get_room_visibility
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/directory/list/room/.+"))
|
|
.respond_with(ok_json(serde_json::json!({"visibility": "public"})))
|
|
.mount(&server).await;
|
|
let r = c.get_room_visibility("!r:localhost").await.unwrap();
|
|
assert_eq!(r.visibility, "public");
|
|
|
|
// set_room_visibility
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/directory/list/room/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.set_room_visibility("!r:localhost", &SetRoomVisibilityRequest {
|
|
visibility: "private".into(),
|
|
}).await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Membership endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn membership_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// join_room_by_id
|
|
Mock::given(method("POST")).and(path_regex("/client/v3/join/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.join_room_by_id("!room:localhost").await.unwrap();
|
|
|
|
// join_room_by_alias — use URL-encoded alias to avoid # being treated as fragment
|
|
c.join_room_by_alias("%23alias:localhost").await.unwrap();
|
|
|
|
// leave_room
|
|
Mock::given(method("POST")).and(path_regex("/client/v3/rooms/.+/leave"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.leave_room("!room:localhost").await.unwrap();
|
|
|
|
// invite
|
|
Mock::given(method("POST")).and(path_regex("/client/v3/rooms/.+/invite"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.invite("!room:localhost", &InviteRequest {
|
|
user_id: "@u:localhost".into(), reason: None,
|
|
}).await.unwrap();
|
|
|
|
// ban
|
|
Mock::given(method("POST")).and(path_regex("/client/v3/rooms/.+/ban"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.ban("!room:localhost", &BanRequest {
|
|
user_id: "@u:localhost".into(), reason: None,
|
|
}).await.unwrap();
|
|
|
|
// unban
|
|
Mock::given(method("POST")).and(path_regex("/client/v3/rooms/.+/unban"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.unban("!room:localhost", &UnbanRequest {
|
|
user_id: "@u:localhost".into(), reason: None,
|
|
}).await.unwrap();
|
|
|
|
// kick
|
|
Mock::given(method("POST")).and(path_regex("/client/v3/rooms/.+/kick"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.kick("!room:localhost", &KickRequest {
|
|
user_id: "@u:localhost".into(), reason: None,
|
|
}).await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// State endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn state_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// get_all_state — use exact path for the room
|
|
Mock::given(method("GET")).and(path("/client/v3/rooms/room1/state"))
|
|
.respond_with(ok_json(serde_json::json!([])))
|
|
.mount(&server).await;
|
|
let r = c.get_all_state("room1").await.unwrap();
|
|
assert!(r.is_empty());
|
|
|
|
// get_state_event — the path includes event_type/state_key, trailing slash for empty key
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/rooms/.+/state/.+/.*"))
|
|
.respond_with(ok_json(serde_json::json!({"name": "Room"})))
|
|
.mount(&server).await;
|
|
let r = c.get_state_event("room2", "m.room.name", "").await.unwrap();
|
|
assert_eq!(r["name"], "Room");
|
|
|
|
// set_state_event
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/rooms/.+/state/.+/.*"))
|
|
.respond_with(ok_json(serde_json::json!({"event_id": "$e1"})))
|
|
.mount(&server).await;
|
|
let r = c.set_state_event(
|
|
"room2", "m.room.name", "",
|
|
&serde_json::json!({"name": "New"}),
|
|
).await.unwrap();
|
|
assert_eq!(r.event_id, "$e1");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sync
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn sync_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// sync — no params
|
|
Mock::given(method("GET")).and(path("/client/v3/sync"))
|
|
.respond_with(ok_json(serde_json::json!({"next_batch": "s1"})))
|
|
.mount(&server).await;
|
|
let r = c.sync(&SyncParams::default()).await.unwrap();
|
|
assert_eq!(r.next_batch, "s1");
|
|
|
|
// sync — all params populated to cover every query-string branch
|
|
let r = c.sync(&SyncParams {
|
|
filter: Some("f".into()),
|
|
since: Some("s0".into()),
|
|
full_state: Some(true),
|
|
set_presence: Some("online".into()),
|
|
timeout: Some(30000),
|
|
}).await.unwrap();
|
|
assert_eq!(r.next_batch, "s1");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Messages / events
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn message_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// send_event
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/rooms/.+/send/.+/.+"))
|
|
.respond_with(ok_json(serde_json::json!({"event_id": "$e1"})))
|
|
.mount(&server).await;
|
|
let r = c.send_event(
|
|
"!r:localhost", "m.room.message", "txn1",
|
|
&serde_json::json!({"msgtype": "m.text", "body": "hi"}),
|
|
).await.unwrap();
|
|
assert_eq!(r.event_id, "$e1");
|
|
|
|
// get_messages — minimal params
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/rooms/.+/messages"))
|
|
.respond_with(ok_json(serde_json::json!({"start": "s0", "chunk": []})))
|
|
.mount(&server).await;
|
|
let r = c.get_messages("!r:localhost", &MessagesParams {
|
|
dir: "b".into(), from: None, to: None, limit: None, filter: None,
|
|
}).await.unwrap();
|
|
assert!(r.chunk.is_empty());
|
|
|
|
// get_messages — all optional params to cover every branch
|
|
let r = c.get_messages("!r:localhost", &MessagesParams {
|
|
dir: "b".into(),
|
|
from: Some("tok1".into()),
|
|
to: Some("tok2".into()),
|
|
limit: Some(10),
|
|
filter: Some("{}".into()),
|
|
}).await.unwrap();
|
|
assert!(r.chunk.is_empty());
|
|
|
|
// get_event
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/rooms/.+/event/.+"))
|
|
.respond_with(ok_json(serde_json::json!({
|
|
"type": "m.room.message",
|
|
"content": {"body": "hi"}
|
|
})))
|
|
.mount(&server).await;
|
|
let r = c.get_event("!r:localhost", "$ev1").await.unwrap();
|
|
assert_eq!(r.event_type, "m.room.message");
|
|
|
|
// get_context — without limit
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/rooms/.+/context/.+"))
|
|
.respond_with(ok_json(serde_json::json!({
|
|
"start": "s0", "end": "s1",
|
|
"events_before": [], "events_after": []
|
|
})))
|
|
.mount(&server).await;
|
|
c.get_context("!r:localhost", "$ev1", None).await.unwrap();
|
|
|
|
// get_context — with limit to cover the branch
|
|
c.get_context("!r:localhost", "$ev1", Some(5)).await.unwrap();
|
|
|
|
// redact
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/rooms/.+/redact/.+/.+"))
|
|
.respond_with(ok_json(serde_json::json!({"event_id": "$re1"})))
|
|
.mount(&server).await;
|
|
let r = c.redact("!r:localhost", "$ev1", "txn2", &RedactRequest { reason: None }).await.unwrap();
|
|
assert_eq!(r.event_id, "$re1");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Presence
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn presence_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// get_presence
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/presence/.+/status"))
|
|
.respond_with(ok_json(serde_json::json!({"presence": "online"})))
|
|
.mount(&server).await;
|
|
let r = c.get_presence("@u:localhost").await.unwrap();
|
|
assert_eq!(r.presence, "online");
|
|
|
|
// set_presence
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/presence/.+/status"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.set_presence("@u:localhost", &SetPresenceRequest {
|
|
presence: "offline".into(), status_msg: None,
|
|
}).await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Typing
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn typing_endpoint() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/rooms/.+/typing/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.send_typing("!r:localhost", "@u:localhost", &TypingRequest {
|
|
typing: true, timeout: Some(30000),
|
|
}).await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Receipts
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn receipt_endpoint() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
Mock::given(method("POST")).and(path_regex("/client/v3/rooms/.+/receipt/.+/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.send_receipt("!r:localhost", "m.read", "$ev1", &ReceiptRequest { thread_id: None }).await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Profile endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn profile_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// get_profile
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/profile/[^/]+$"))
|
|
.respond_with(ok_json(serde_json::json!({"displayname": "Alice"})))
|
|
.mount(&server).await;
|
|
let r = c.get_profile("@u:localhost").await.unwrap();
|
|
assert_eq!(r.displayname.as_deref(), Some("Alice"));
|
|
|
|
// get_displayname
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/profile/.+/displayname"))
|
|
.respond_with(ok_json(serde_json::json!({"displayname": "Alice"})))
|
|
.mount(&server).await;
|
|
let r = c.get_displayname("@u:localhost").await.unwrap();
|
|
assert_eq!(r.displayname.as_deref(), Some("Alice"));
|
|
|
|
// set_displayname
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/profile/.+/displayname"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.set_displayname("@u:localhost", &SetDisplaynameRequest {
|
|
displayname: "Bob".into(),
|
|
}).await.unwrap();
|
|
|
|
// get_avatar_url
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/profile/.+/avatar_url"))
|
|
.respond_with(ok_json(serde_json::json!({"avatar_url": "mxc://example/abc"})))
|
|
.mount(&server).await;
|
|
let r = c.get_avatar_url("@u:localhost").await.unwrap();
|
|
assert_eq!(r.avatar_url.as_deref(), Some("mxc://example/abc"));
|
|
|
|
// set_avatar_url
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/profile/.+/avatar_url"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.set_avatar_url("@u:localhost", &SetAvatarUrlRequest {
|
|
avatar_url: "mxc://example/xyz".into(),
|
|
}).await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Alias endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn alias_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// create_alias
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/directory/room/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.create_alias("%23test:localhost", &CreateAliasRequest {
|
|
room_id: "!r:localhost".into(),
|
|
}).await.unwrap();
|
|
|
|
// resolve_alias
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/directory/room/.+"))
|
|
.respond_with(ok_json(serde_json::json!({
|
|
"room_id": "!r:localhost", "servers": ["localhost"]
|
|
})))
|
|
.mount(&server).await;
|
|
let r = c.resolve_alias("%23test:localhost").await.unwrap();
|
|
assert_eq!(r.room_id.as_deref(), Some("!r:localhost"));
|
|
|
|
// delete_alias
|
|
Mock::given(method("DELETE")).and(path_regex("/client/v3/directory/room/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.delete_alias("%23test:localhost").await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// User directory search
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn search_users_endpoint() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
Mock::given(method("POST")).and(path("/client/v3/user_directory/search"))
|
|
.respond_with(ok_json(serde_json::json!({"results": [], "limited": false})))
|
|
.mount(&server).await;
|
|
let r = c.search_users(&UserSearchRequest {
|
|
search_term: "alice".into(), limit: None,
|
|
}).await.unwrap();
|
|
assert!(r.results.is_empty());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Media endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn media_upload() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// upload_media — success
|
|
Mock::given(method("POST")).and(path("/media/v3/upload"))
|
|
.respond_with(ok_json(serde_json::json!({"content_uri": "mxc://example/abc"})))
|
|
.mount(&server).await;
|
|
let r = c.upload_media("image/png", b"fake-png".to_vec()).await.unwrap();
|
|
assert_eq!(r.content_uri, "mxc://example/abc");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn media_upload_http_error() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// upload_media — HTTP error branch
|
|
Mock::given(method("POST")).and(path("/media/v3/upload"))
|
|
.respond_with(ResponseTemplate::new(500).set_body_string("server error"))
|
|
.mount(&server).await;
|
|
let r = c.upload_media("image/png", b"fake-png".to_vec()).await;
|
|
assert!(r.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn media_upload_bad_json() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// upload_media — invalid JSON response branch
|
|
Mock::given(method("POST")).and(path("/media/v3/upload"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_string("not-json"))
|
|
.mount(&server).await;
|
|
let r = c.upload_media("image/png", b"fake-png".to_vec()).await;
|
|
assert!(r.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn media_download_and_thumbnail() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// download_media
|
|
Mock::given(method("GET")).and(path_regex("/media/v3/download/.+/.+"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_bytes(b"fake-content".to_vec()))
|
|
.mount(&server).await;
|
|
let r = c.download_media("localhost", "abc123").await.unwrap();
|
|
assert_eq!(r.as_ref(), b"fake-content");
|
|
|
|
// thumbnail — without method param
|
|
Mock::given(method("GET")).and(path_regex("/media/v3/thumbnail/.+/.+"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_bytes(b"thumb-bytes".to_vec()))
|
|
.mount(&server).await;
|
|
let r = c.thumbnail("localhost", "abc123", &ThumbnailParams {
|
|
width: 64, height: 64, method: None,
|
|
}).await.unwrap();
|
|
assert_eq!(r.as_ref(), b"thumb-bytes");
|
|
|
|
// thumbnail — with method param to cover the branch
|
|
let r = c.thumbnail("localhost", "abc123", &ThumbnailParams {
|
|
width: 64, height: 64, method: Some("crop".into()),
|
|
}).await.unwrap();
|
|
assert_eq!(r.as_ref(), b"thumb-bytes");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Device endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn device_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// list_devices
|
|
Mock::given(method("GET")).and(path("/client/v3/devices"))
|
|
.respond_with(ok_json(serde_json::json!({"devices": []})))
|
|
.mount(&server).await;
|
|
let r = c.list_devices().await.unwrap();
|
|
assert!(r.devices.is_empty());
|
|
|
|
// get_device
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/devices/.+"))
|
|
.respond_with(ok_json(serde_json::json!({"device_id": "D1"})))
|
|
.mount(&server).await;
|
|
let r = c.get_device("D1").await.unwrap();
|
|
assert_eq!(r.device_id, "D1");
|
|
|
|
// update_device
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/devices/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.update_device("D1", &UpdateDeviceRequest {
|
|
display_name: Some("Phone".into()),
|
|
}).await.unwrap();
|
|
|
|
// delete_device
|
|
Mock::given(method("DELETE")).and(path_regex("/client/v3/devices/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.delete_device("D1", &DeleteDeviceRequest { auth: None }).await.unwrap();
|
|
|
|
// batch_delete_devices
|
|
Mock::given(method("POST")).and(path("/client/v3/delete_devices"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.batch_delete_devices(&BatchDeleteDevicesRequest {
|
|
devices: vec!["D1".into(), "D2".into()], auth: None,
|
|
}).await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// E2EE / Keys
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn keys_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// upload_keys
|
|
Mock::given(method("POST")).and(path("/client/v3/keys/upload"))
|
|
.respond_with(ok_json(serde_json::json!({"one_time_key_counts": {}})))
|
|
.mount(&server).await;
|
|
let r = c.upload_keys(&KeysUploadRequest {
|
|
device_keys: None, one_time_keys: None, fallback_keys: None,
|
|
}).await.unwrap();
|
|
assert!(r.one_time_key_counts.is_object());
|
|
|
|
// query_keys
|
|
Mock::given(method("POST")).and(path("/client/v3/keys/query"))
|
|
.respond_with(ok_json(serde_json::json!({"device_keys": {}})))
|
|
.mount(&server).await;
|
|
let r = c.query_keys(&KeysQueryRequest {
|
|
device_keys: serde_json::json!({}), timeout: None,
|
|
}).await.unwrap();
|
|
assert!(r.device_keys.is_object());
|
|
|
|
// claim_keys
|
|
Mock::given(method("POST")).and(path("/client/v3/keys/claim"))
|
|
.respond_with(ok_json(serde_json::json!({"one_time_keys": {}})))
|
|
.mount(&server).await;
|
|
let r = c.claim_keys(&KeysClaimRequest {
|
|
one_time_keys: serde_json::json!({}), timeout: None,
|
|
}).await.unwrap();
|
|
assert!(r.one_time_keys.is_object());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Push endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn push_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// list_pushers
|
|
Mock::given(method("GET")).and(path("/client/v3/pushers"))
|
|
.respond_with(ok_json(serde_json::json!({"pushers": []})))
|
|
.mount(&server).await;
|
|
let r = c.list_pushers().await.unwrap();
|
|
assert!(r.pushers.is_empty());
|
|
|
|
// set_pusher
|
|
Mock::given(method("POST")).and(path("/client/v3/pushers/set"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.set_pusher(&serde_json::json!({})).await.unwrap();
|
|
|
|
// get_push_rules
|
|
Mock::given(method("GET")).and(path("/client/v3/pushrules/"))
|
|
.respond_with(ok_json(serde_json::json!({"global": {}})))
|
|
.mount(&server).await;
|
|
let r = c.get_push_rules().await.unwrap();
|
|
assert!(r.global.is_some());
|
|
|
|
// set_push_rule
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/pushrules/.+/.+/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.set_push_rule("global", "content", "rule1", &serde_json::json!({})).await.unwrap();
|
|
|
|
// delete_push_rule
|
|
Mock::given(method("DELETE")).and(path_regex("/client/v3/pushrules/.+/.+/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.delete_push_rule("global", "content", "rule1").await.unwrap();
|
|
|
|
// get_notifications — no params
|
|
Mock::given(method("GET")).and(path("/client/v3/notifications"))
|
|
.respond_with(ok_json(serde_json::json!({"notifications": []})))
|
|
.mount(&server).await;
|
|
let r = c.get_notifications(&NotificationsParams::default()).await.unwrap();
|
|
assert!(r.notifications.is_empty());
|
|
|
|
// get_notifications — all params to cover all branches
|
|
let r = c.get_notifications(&NotificationsParams {
|
|
from: Some("tok".into()),
|
|
limit: Some(5),
|
|
only: Some("highlight".into()),
|
|
}).await.unwrap();
|
|
assert!(r.notifications.is_empty());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Account data
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn account_data_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// get_account_data
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/user/.+/account_data/.+"))
|
|
.respond_with(ok_json(serde_json::json!({"key": "val"})))
|
|
.mount(&server).await;
|
|
let r = c.get_account_data("@u:localhost", "m.some_type").await.unwrap();
|
|
assert_eq!(r["key"], "val");
|
|
|
|
// set_account_data
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/user/.+/account_data/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.set_account_data("@u:localhost", "m.some_type", &serde_json::json!({"key": "val"})).await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tags
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn tag_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// get_tags
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/user/.+/rooms/.+/tags$"))
|
|
.respond_with(ok_json(serde_json::json!({"tags": {}})))
|
|
.mount(&server).await;
|
|
let r = c.get_tags("@u:localhost", "!r:localhost").await.unwrap();
|
|
assert!(r.tags.is_object());
|
|
|
|
// set_tag
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/user/.+/rooms/.+/tags/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.set_tag("@u:localhost", "!r:localhost", "m.favourite", &serde_json::json!({})).await.unwrap();
|
|
|
|
// delete_tag
|
|
Mock::given(method("DELETE")).and(path_regex("/client/v3/user/.+/rooms/.+/tags/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.delete_tag("@u:localhost", "!r:localhost", "m.favourite").await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Search
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn search_messages_endpoint() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
Mock::given(method("POST")).and(path("/client/v3/search"))
|
|
.respond_with(ok_json(serde_json::json!({"search_categories": {}})))
|
|
.mount(&server).await;
|
|
let r = c.search_messages(&SearchRequest {
|
|
search_categories: serde_json::json!({}),
|
|
}).await.unwrap();
|
|
assert!(r.search_categories.is_object());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Filters
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn filter_endpoints() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
// create_filter
|
|
Mock::given(method("POST")).and(path_regex("/client/v3/user/.+/filter$"))
|
|
.respond_with(ok_json(serde_json::json!({"filter_id": "f1"})))
|
|
.mount(&server).await;
|
|
let r = c.create_filter("@u:localhost", &serde_json::json!({})).await.unwrap();
|
|
assert_eq!(r.filter_id, "f1");
|
|
|
|
// get_filter
|
|
Mock::given(method("GET")).and(path_regex("/client/v3/user/.+/filter/.+"))
|
|
.respond_with(ok_json(serde_json::json!({})))
|
|
.mount(&server).await;
|
|
c.get_filter("@u:localhost", "f1").await.unwrap();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Spaces
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn space_hierarchy_endpoint() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
Mock::given(method("GET")).and(path_regex("/client/v1/rooms/.+/hierarchy"))
|
|
.respond_with(ok_json(serde_json::json!({"rooms": []})))
|
|
.mount(&server).await;
|
|
|
|
// no params
|
|
let r = c.get_space_hierarchy("!r:localhost", &SpaceHierarchyParams::default()).await.unwrap();
|
|
assert!(r.rooms.is_empty());
|
|
|
|
// all params to cover all branches
|
|
let r = c.get_space_hierarchy("!r:localhost", &SpaceHierarchyParams {
|
|
from: Some("tok".into()),
|
|
limit: Some(10),
|
|
max_depth: Some(3),
|
|
suggested_only: Some(true),
|
|
}).await.unwrap();
|
|
assert!(r.rooms.is_empty());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Send-to-device
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn send_to_device_endpoint() {
|
|
let server = MockServer::start().await;
|
|
let c = client(&server.uri());
|
|
|
|
Mock::given(method("PUT")).and(path_regex("/client/v3/sendToDevice/.+/.+"))
|
|
.respond_with(ok_empty())
|
|
.mount(&server).await;
|
|
c.send_to_device("m.room.encrypted", "txn1", &SendToDeviceRequest {
|
|
messages: serde_json::json!({}),
|
|
}).await.unwrap();
|
|
}
|