1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
1415//! Augmented [`ClientBuilder`] that can set up an already logged-in user.
1617use matrix_sdk_base::{
18 store::{RoomLoadSettings, StoreConfig},
19 SessionMeta,
20};
21use ruma::{api::MatrixVersion, owned_device_id, owned_user_id};
2223use crate::{
24 authentication::matrix::MatrixSession, config::RequestConfig, Client, ClientBuilder,
25 SessionTokens,
26};
2728/// An augmented [`ClientBuilder`] that also allows for handling session login.
29#[allow(missing_debug_implementations)]
30pub struct MockClientBuilder {
31 builder: ClientBuilder,
32 auth_state: AuthState,
33}
3435impl MockClientBuilder {
36/// Create a new [`MockClientBuilder`] connected to the given homeserver,
37 /// using Matrix V1.12, and which will not attempt any network retry (by
38 /// default).
39pub(crate) fn new(homeserver: String) -> Self {
40let default_builder = Client::builder()
41 .homeserver_url(&homeserver)
42 .server_versions([MatrixVersion::V1_12])
43 .request_config(RequestConfig::new().disable_retry());
4445Self { builder: default_builder, auth_state: AuthState::LoggedInWithMatrixAuth }
46 }
4748/// Doesn't log-in a user.
49 ///
50 /// Authenticated requests will fail if this is called.
51pub fn unlogged(mut self) -> Self {
52self.auth_state = AuthState::None;
53self
54}
5556/// The client is registered with the OAuth 2.0 API.
57pub fn registered_with_oauth(mut self, issuer: impl Into<String>) -> Self {
58self.auth_state = AuthState::RegisteredWithOAuth { issuer: issuer.into() };
59self
60}
6162/// The user is already logged in with the OAuth 2.0 API.
63pub fn logged_in_with_oauth(mut self, issuer: impl Into<String>) -> Self {
64self.auth_state = AuthState::LoggedInWithOAuth { issuer: issuer.into() };
65self
66}
6768/// Provides another [`StoreConfig`] for the underlying [`ClientBuilder`].
69pub fn store_config(mut self, store_config: StoreConfig) -> Self {
70self.builder = self.builder.store_config(store_config);
71self
72}
7374/// Use an SQLite store at the given path for the underlying
75 /// [`ClientBuilder`].
76#[cfg(feature = "sqlite")]
77pub fn sqlite_store(mut self, path: impl AsRef<std::path::Path>) -> Self {
78self.builder = self.builder.sqlite_store(path, None);
79self
80}
8182/// Handle refreshing access tokens automatically.
83pub fn handle_refresh_tokens(mut self) -> Self {
84self.builder = self.builder.handle_refresh_tokens();
85self
86}
8788/// Finish building the client into the final [`Client`] instance.
89pub async fn build(self) -> Client {
90let client = self.builder.build().await.expect("building client failed");
91self.auth_state.maybe_restore_client(&client).await;
9293 client
94 }
95}
9697/// The possible authentication states of a [`Client`] built with
98/// [`MockClientBuilder`].
99enum AuthState {
100/// The client is not logged in.
101None,
102/// The client is logged in with the native Matrix API.
103LoggedInWithMatrixAuth,
104/// The client is registered with the OAuth 2.0 API.
105RegisteredWithOAuth { issuer: String },
106/// The client is logged in with the OAuth 2.0 API.
107LoggedInWithOAuth { issuer: String },
108}
109110impl AuthState {
111/// Restore the given [`Client`] according to this [`AuthState`], if
112 /// necessary.
113async fn maybe_restore_client(self, client: &Client) {
114match self {
115 AuthState::None => {}
116 AuthState::LoggedInWithMatrixAuth => {
117 client
118 .matrix_auth()
119 .restore_session(mock_matrix_session(), RoomLoadSettings::default())
120 .await
121.unwrap();
122 }
123 AuthState::RegisteredWithOAuth { issuer } => {
124let issuer = url::Url::parse(&issuer).unwrap();
125 client.oauth().restore_registered_client(issuer, oauth::mock_client_id());
126 }
127 AuthState::LoggedInWithOAuth { issuer } => {
128 client
129 .oauth()
130 .restore_session(
131 oauth::mock_session(mock_session_tokens_with_refresh(), issuer),
132 RoomLoadSettings::default(),
133 )
134 .await
135.unwrap();
136 }
137 }
138 }
139}
140141/// A [`SessionMeta`], for unit or integration tests.
142pub fn mock_session_meta() -> SessionMeta {
143 SessionMeta {
144 user_id: owned_user_id!("@example:localhost"),
145 device_id: owned_device_id!("DEVICEID"),
146 }
147}
148149/// A [`SessionTokens`] including only an access token, for unit or integration
150/// tests.
151pub fn mock_session_tokens() -> SessionTokens {
152 SessionTokens { access_token: "1234".to_owned(), refresh_token: None }
153}
154155/// A [`SessionTokens`] including an access token and a refresh token, for unit
156/// or integration tests.
157pub fn mock_session_tokens_with_refresh() -> SessionTokens {
158 SessionTokens { access_token: "1234".to_owned(), refresh_token: Some("ZYXWV".to_owned()) }
159}
160161/// Different session tokens than the ones returned by
162/// [`mock_session_tokens_with_refresh()`].
163pub fn mock_prev_session_tokens_with_refresh() -> SessionTokens {
164 SessionTokens {
165 access_token: "prev-access-token".to_owned(),
166 refresh_token: Some("prev-refresh-token".to_owned()),
167 }
168}
169170/// A [`MatrixSession`], for unit or integration tests.
171pub fn mock_matrix_session() -> MatrixSession {
172 MatrixSession { meta: mock_session_meta(), tokens: mock_session_tokens() }
173}
174175/// Mock client data for the OAuth 2.0 API.
176pub mod oauth {
177use ruma::serde::Raw;
178use url::Url;
179180use crate::{
181 authentication::oauth::{
182 registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
183 ClientId, OAuthSession, UserSession,
184 },
185 SessionTokens,
186 };
187188/// An OAuth 2.0 `ClientId`, for unit or integration tests.
189pub fn mock_client_id() -> ClientId {
190 ClientId::new("test_client_id".to_owned())
191 }
192193/// A redirect URI, for unit or integration tests.
194pub fn mock_redirect_uri() -> Url {
195 Url::parse("http://127.0.0.1/").expect("redirect URI should be valid")
196 }
197198/// `VerifiedClientMetadata` that should be valid in most cases, for unit or
199 /// integration tests.
200pub fn mock_client_metadata() -> Raw<ClientMetadata> {
201let client_uri = Url::parse("https://github.com/matrix-org/matrix-rust-sdk")
202 .expect("client URI should be valid");
203204let mut metadata = ClientMetadata::new(
205 ApplicationType::Native,
206vec![
207 OAuthGrantType::AuthorizationCode { redirect_uris: vec![mock_redirect_uri()] },
208 OAuthGrantType::DeviceCode,
209 ],
210 Localized::new(client_uri, None),
211 );
212 metadata.client_name = Some(Localized::new("matrix-rust-sdk-test".to_owned(), None));
213214 Raw::new(&metadata).expect("client metadata should serialize successfully")
215 }
216217/// An [`OAuthSession`] to restore, for unit or integration tests.
218pub fn mock_session(tokens: SessionTokens, issuer: impl AsRef<str>) -> OAuthSession {
219let issuer = Url::parse(issuer.as_ref()).unwrap();
220221 OAuthSession {
222 client_id: mock_client_id(),
223 user: UserSession { meta: super::mock_session_meta(), tokens, issuer },
224 }
225 }
226}