matrix-sdk 0.17.0

A high level Matrix client-server library.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
use std::sync::Arc;

use matrix_sdk_base::{StateStoreDataKey, StateStoreDataValue, StoreError, ttl::TtlValue};
use ruma::{
    api::{
        Metadata,
        client::{
            discovery::get_capabilities::{
                self,
                v3::{
                    AccountModerationCapability, Capabilities, ProfileFieldsCapability,
                    RoomVersionsCapability,
                },
            },
            profile::delete_profile_field,
        },
    },
    profile::ProfileFieldName,
};
use tracing::{debug, warn};

use crate::{Client, HttpError, HttpResult, client::caches::CachedValue};

/// Helper to check what [`Capabilities`] are supported by the homeserver.
///
/// Spec: <https://spec.matrix.org/latest/client-server-api/#capabilities-negotiation>
#[derive(Debug, Clone)]
pub struct HomeserverCapabilities {
    client: Client,
}

impl HomeserverCapabilities {
    /// Creates a new [`HomeserverCapabilities`] instance.
    pub fn new(client: Client) -> Self {
        Self { client }
    }

    /// Forces a refresh of the cached value using the `/capabilities` endpoint.
    pub async fn refresh(&self) -> crate::Result<()> {
        self.get_and_cache_remote_capabilities().await?;
        Ok(())
    }

    /// Returns whether the user can change their password or not.
    pub async fn can_change_password(&self) -> crate::Result<bool> {
        let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
        Ok(capabilities.change_password.enabled)
    }

    /// Returns whether the user can change their display name or not.
    ///
    /// This will first check the `m.profile_fields` capability and use it if
    /// present, or fall back to `m.set_displayname` otherwise.
    ///
    /// Spec: <https://spec.matrix.org/latest/client-server-api/#mset_displayname-capability>
    pub async fn can_change_displayname(&self) -> crate::Result<bool> {
        let capabilities = self.profile_capabilities().await?;

        if let Some(profile_fields) = capabilities.profile_fields {
            Ok(profile_fields.can_set_field(&ProfileFieldName::DisplayName))
        } else {
            Ok(capabilities.set_displayname)
        }
    }

    /// Returns whether the user can change their avatar or not.
    ///
    /// This will first check the `m.profile_fields` capability and use it if
    /// present, or fall back to `m.set_avatar_url` otherwise.
    ///
    /// Spec: <https://spec.matrix.org/latest/client-server-api/#mset_avatar_url-capability>
    pub async fn can_change_avatar(&self) -> crate::Result<bool> {
        let capabilities = self.profile_capabilities().await?;

        if let Some(profile_fields) = capabilities.profile_fields {
            Ok(profile_fields.can_set_field(&ProfileFieldName::AvatarUrl))
        } else {
            Ok(capabilities.set_avatar_url)
        }
    }

    /// Returns whether the user can add, remove, or change 3PID associations on
    /// their account.
    ///
    /// Spec: <https://spec.matrix.org/latest/client-server-api/#m3pid_changes-capability>
    pub async fn can_change_thirdparty_ids(&self) -> crate::Result<bool> {
        let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
        Ok(capabilities.thirdparty_id_changes.enabled)
    }

    /// Returns whether the user is able to use `POST /login/get_token` to
    /// generate single-use, time-limited tokens to log unauthenticated
    /// clients into their account.
    ///
    /// When not listed, clients SHOULD assume the user is unable to generate
    /// tokens.
    ///
    /// Spec: <https://spec.matrix.org/latest/client-server-api/#mget_login_token-capability>
    pub async fn can_get_login_token(&self) -> crate::Result<bool> {
        let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
        Ok(capabilities.get_login_token.enabled)
    }

    /// Returns which profile fields the user is able to change.
    ///
    /// Spec: <https://spec.matrix.org/latest/client-server-api/#mprofile_fields-capability>
    pub async fn extended_profile_fields(&self) -> crate::Result<ProfileFieldsCapability> {
        Ok(self
            .profile_capabilities()
            .await?
            .profile_fields
            .unwrap_or_else(|| ProfileFieldsCapability::new(false)))
    }

    /// Returns the room versions supported by the server.
    ///
    /// Spec: <https://spec.matrix.org/latest/client-server-api/#mroom_versions-capability>
    pub async fn room_versions(&self) -> crate::Result<RoomVersionsCapability> {
        let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
        Ok(capabilities.room_versions)
    }

    /// Returns whether the user can perform account moderation actions.
    ///
    /// Spec: <https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3capabilities_response-200_accountmoderationcapability>
    pub async fn account_moderation(&self) -> crate::Result<AccountModerationCapability> {
        let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
        Ok(capabilities.account_moderation)
    }

    /// Returns whether or not the server automatically forgets rooms which the
    /// user has left.
    ///
    /// Spec: <https://spec.matrix.org/latest/client-server-api/#mforget_forced_upon_leave-capability>
    pub async fn forgets_room_when_leaving(&self) -> crate::Result<bool> {
        let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
        Ok(capabilities.forget_forced_upon_leave.enabled)
    }

    /// Gets the supported [`Capabilities`] from the local cache.
    async fn homeserver_capabilities_cached(&self) -> Result<Option<Capabilities>, StoreError> {
        let capabilities_cache = &self.client.inner.caches.homeserver_capabilities;

        let value = if let CachedValue::Cached(cached) = capabilities_cache.value() {
            cached
        } else if let Some(stored) = self
            .client
            .state_store()
            .get_kv_data(StateStoreDataKey::HomeserverCapabilities)
            .await?
            .and_then(|value| value.into_homeserver_capabilities())
        {
            // Copy the data from the store in the in-memory cache.
            capabilities_cache.set_value(stored.clone());

            stored
        } else {
            return Ok(None);
        };

        // Spawn a task to refresh the cache if it has expired.
        if value.has_expired() {
            debug!("spawning task to refresh homeserver capabilities cache");

            let homeserver_capabilities = self.clone();
            self.client.task_monitor().spawn_finite_task(
                "refresh homeserver capabilities cache",
                async move {
                    if let Err(error) =
                        homeserver_capabilities.get_and_cache_remote_capabilities().await
                    {
                        warn!("failed to refresh homeserver capabilities cache: {error}");
                    }
                },
            );
        }

        Ok(Some(value.into_data()))
    }

    /// Gets the supported [`Capabilities`] either from the local cache or from
    /// the homeserver using the `/capabilities` endpoint if the data is not
    /// cached.
    ///
    /// To ensure you get updated values, you should call [`Self::refresh`]
    /// instead.
    async fn load_or_fetch_homeserver_capabilities(&self) -> crate::Result<Capabilities> {
        match self.homeserver_capabilities_cached().await {
            Ok(Some(capabilities)) => {
                return Ok(capabilities);
            }
            Ok(None) => {
                // fallthrough: cache is empty
            }
            Err(err) => {
                warn!("error when loading cached homeserver capabilities: {err}");
                // fallthrough to network.
            }
        }

        Ok(self.get_and_cache_remote_capabilities().await?)
    }

    /// Gets and caches the capabilities of the homeserver.
    async fn get_and_cache_remote_capabilities(&self) -> HttpResult<Capabilities> {
        let capabilities_cache = &self.client.inner.caches.homeserver_capabilities;

        let mut capabilities_guard = match capabilities_cache.refresh_lock.try_lock() {
            Ok(guard) => guard,
            Err(_) => {
                // There is already a refresh in progress, wait for it to finish.
                let guard = capabilities_cache.refresh_lock.lock().await;

                if let Err(error) = guard.as_ref() {
                    // There was an error in the previous refresh, return it.
                    return Err(HttpError::Cached(error.clone()));
                }

                // Reuse the data if it was cached and it hasn't expired.
                if let CachedValue::Cached(value) = capabilities_cache.value()
                    && !value.has_expired()
                {
                    return Ok(value.into_data());
                }

                // The data wasn't cached or has expired, we need to make another request.
                guard
            }
        };

        let capabilities = match self.client.send(get_capabilities::v3::Request::new()).await {
            Ok(response) => {
                *capabilities_guard = Ok(());
                TtlValue::new(response.capabilities)
            }
            Err(error) => {
                let error = Arc::new(error);
                *capabilities_guard = Err(error.clone());
                return Err(HttpError::Cached(error));
            }
        };

        if let Err(err) = self
            .client
            .state_store()
            .set_kv_data(
                StateStoreDataKey::HomeserverCapabilities,
                StateStoreDataValue::HomeserverCapabilities(capabilities.clone()),
            )
            .await
        {
            warn!("error when caching homeserver capabilities: {err}");
        }

        capabilities_cache.set_value(capabilities.clone());

        Ok(capabilities.into_data())
    }

    /// Gets or computes the supported [`ProfileCapabilities`].
    async fn profile_capabilities(&self) -> crate::Result<ProfileCapabilities> {
        let capabilities = self.load_or_fetch_homeserver_capabilities().await?;

        let profile_fields = match capabilities.profile_fields {
            Some(profile_fields) => Some(profile_fields),
            None => {
                // According to the Matrix spec about the `m.profile_fields` capability:
                //
                // > When this capability is not listed, clients SHOULD assume the user is
                // > able to change profile fields without any restrictions, provided the
                // > homeserver advertises a specification version that includes the
                // > `m.profile_fields` capability in the `/versions` response.
                if self.homeserver_supports_extended_profile_fields().await? {
                    Some(ProfileFieldsCapability::new(true))
                } else {
                    None
                }
            }
        };

        #[allow(deprecated)]
        Ok(ProfileCapabilities {
            profile_fields,
            set_displayname: capabilities.set_displayname.enabled,
            set_avatar_url: capabilities.set_avatar_url.enabled,
        })
    }

    /// Whether the homeserver supports extended profile fields.
    ///
    ///
    /// [Matrix spec]: https://spec.matrix.org/latest/client-server-api/#mprofile_fields-capability
    async fn homeserver_supports_extended_profile_fields(&self) -> crate::Result<bool> {
        let supported_versions = self.client.supported_versions().await?;
        // If the homeserver supports the endpoint to delete profile fields, it supports
        // extended profile fields.
        Ok(delete_profile_field::v3::Request::PATH_BUILDER.is_supported(&supported_versions))
    }
}

/// All the capabilities to change a profile field.
struct ProfileCapabilities {
    /// The capability to change profile fields, advertised by the homeserver or
    /// computed.
    profile_fields: Option<ProfileFieldsCapability>,
    /// The capability to set the display name advertised by the homeserver.
    set_displayname: bool,
    /// The capability to set the avatar URL advertised by the homeserver.
    set_avatar_url: bool,
}

#[cfg(all(not(target_family = "wasm"), test))]
mod tests {
    use std::time::Duration;

    use assert_matches::assert_matches;
    use matrix_sdk_base::sleep::sleep;
    use matrix_sdk_test::async_test;
    #[allow(deprecated)]
    use ruma::api::{
        MatrixVersion,
        client::discovery::get_capabilities::v3::{
            SetAvatarUrlCapability, SetDisplayNameCapability,
        },
    };

    use super::*;
    use crate::test_utils::mocks::MatrixMockServer;

    #[async_test]
    async fn test_refresh_always_updates_capabilities() {
        let server = MatrixMockServer::new().await;
        let client = server.client_builder().build().await;

        // Set the expected capabilities to something we can check
        let mut expected_capabilities = Capabilities::default();
        expected_capabilities.change_password.enabled = true;
        server
            .mock_get_homeserver_capabilities()
            .ok_with_capabilities(expected_capabilities)
            .mock_once()
            .mount()
            .await;

        // Refresh the capabilities
        let capabilities = client.homeserver_capabilities();
        capabilities.refresh().await.expect("refreshing capabilities failed");

        // Check the values we get are updated
        assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));

        let mut expected_capabilities = Capabilities::default();
        expected_capabilities.change_password.enabled = false;
        server
            .mock_get_homeserver_capabilities()
            .ok_with_capabilities(expected_capabilities)
            .mock_once()
            .mount()
            .await;

        // Check the values we get are not updated without a refresh, they're loaded
        // from the cache
        assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));

        // Do another refresh to make sure we get the updated values
        capabilities.refresh().await.expect("refreshing capabilities failed");

        // Check the values we get are updated
        assert!(!capabilities.can_change_password().await.expect("checking capabilities failed"));
    }

    #[async_test]
    async fn test_get_functions_refresh_the_data_if_not_available_or_use_cache_if_available() {
        let server = MatrixMockServer::new().await;
        let client = server.client_builder().build().await;

        // Set the expected capabilities to something we can check
        let mut expected_capabilities = Capabilities::default();
        let mut profile_fields = ProfileFieldsCapability::new(true);
        profile_fields.allowed = Some(vec![ProfileFieldName::DisplayName]);
        expected_capabilities.profile_fields = Some(profile_fields);
        server
            .mock_get_homeserver_capabilities()
            .ok_with_capabilities(expected_capabilities)
            // Ensure it's called just once
            .mock_once()
            .mount()
            .await;

        // Refresh the capabilities
        let capabilities = client.homeserver_capabilities();

        // Check the values we get are updated
        assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));

        // Now revert the previous mock so we can check we're getting the cached value
        // instead of this one
        let mut expected_capabilities = Capabilities::default();
        let mut profile_fields = ProfileFieldsCapability::new(true);
        profile_fields.disallowed = Some(vec![ProfileFieldName::DisplayName]);
        expected_capabilities.profile_fields = Some(profile_fields);
        server
            .mock_get_homeserver_capabilities()
            .ok_with_capabilities(expected_capabilities)
            .expect(1)
            .mount()
            .await;

        // Check the values we get are not updated without a refresh, they're loaded
        // from the cache
        assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));

        // Force an expiry of the data.
        let capabilities_data =
            capabilities.homeserver_capabilities_cached().await.unwrap().unwrap();
        let mut ttl_value = TtlValue::new(capabilities_data);
        ttl_value.expire();
        client.inner.caches.homeserver_capabilities.set_value(ttl_value);

        // Call a method to trigger a cache refresh background task.
        capabilities.homeserver_capabilities_cached().await.unwrap().unwrap();

        // We wait for the task to finish, the endpoint should have been called again.
        sleep(Duration::from_secs(1)).await;
        assert_matches!(client.inner.caches.homeserver_capabilities.value(), CachedValue::Cached(value) if !value.has_expired());
    }

    #[async_test]
    #[allow(deprecated)]
    async fn test_deprecated_profile_fields_capabilities() {
        let server = MatrixMockServer::new().await;

        // The user can only set the display name but not the avatar url or extended
        // profile fields.
        let mut capabilities = Capabilities::new();
        capabilities.profile_fields.take();
        capabilities.set_displayname = SetDisplayNameCapability::new(true);
        capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
        server
            .mock_get_homeserver_capabilities()
            .ok_with_capabilities(capabilities)
            // It should be called once by each client below.
            .expect(2)
            .mount()
            .await;

        // Client with Matrix 1.12 that did not support extended profile fields yet.
        // Because there is no `m.profile_fields` capability, we rely on the legacy
        // profile capabilities.
        let client =
            server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
        let capabilities_api = client.homeserver_capabilities();
        assert!(
            capabilities_api
                .can_change_displayname()
                .await
                .expect("checking displayname capability failed")
        );
        assert!(
            !capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
        );
        assert!(
            !capabilities_api
                .extended_profile_fields()
                .await
                .expect("checking profile fields capability failed")
                .enabled
        );

        // Client with Matrix 1.16 that added support for extended profile fields, the
        // deprecated profile capabilities are ignored.
        let client =
            server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
        let capabilities_api = client.homeserver_capabilities();
        assert!(
            capabilities_api
                .can_change_displayname()
                .await
                .expect("checking displayname capability failed")
        );
        assert!(
            capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
        );
        assert!(
            capabilities_api
                .extended_profile_fields()
                .await
                .expect("checking profile fields capability failed")
                .enabled
        );
    }

    #[async_test]
    #[allow(deprecated)]
    async fn test_extended_profile_fields_capabilities_enabled() {
        let server = MatrixMockServer::new().await;

        // The user can set any profile field.
        // The legacy capabilities say differently, but they will be ignored.
        let mut capabilities = Capabilities::new();
        capabilities.profile_fields = Some(ProfileFieldsCapability::new(true));
        capabilities.set_displayname = SetDisplayNameCapability::new(true);
        capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
        server
            .mock_get_homeserver_capabilities()
            .ok_with_capabilities(capabilities)
            // It should be called once by each client below.
            .expect(2)
            .mount()
            .await;

        // Client with Matrix 1.12 that did not support extended profile fields yet.
        // However, because there is an `m.profile_fields` capability, we still rely on
        // it.
        let client =
            server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
        let capabilities_api = client.homeserver_capabilities();
        assert!(
            capabilities_api
                .can_change_displayname()
                .await
                .expect("checking displayname capability failed")
        );
        assert!(
            capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
        );
        assert!(
            capabilities_api
                .extended_profile_fields()
                .await
                .expect("checking profile fields capability failed")
                .enabled
        );

        // Client with Matrix 1.16 that added support for extended profile fields, only
        // the `m.profile_fields` capability is used too.
        let client =
            server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
        let capabilities_api = client.homeserver_capabilities();
        assert!(
            capabilities_api
                .can_change_displayname()
                .await
                .expect("checking displayname capability failed")
        );
        assert!(
            capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
        );
        assert!(
            capabilities_api
                .extended_profile_fields()
                .await
                .expect("checking profile fields capability failed")
                .enabled
        );
    }

    #[async_test]
    #[allow(deprecated)]
    async fn test_extended_profile_fields_capabilities_disabled() {
        let server = MatrixMockServer::new().await;

        // The user cannot set any profile field.
        // The legacy capabilities say differently, but they will be ignored.
        let mut capabilities = Capabilities::new();
        capabilities.profile_fields = Some(ProfileFieldsCapability::new(false));
        capabilities.set_displayname = SetDisplayNameCapability::new(true);
        capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
        server
            .mock_get_homeserver_capabilities()
            .ok_with_capabilities(capabilities)
            // It should be called once by each client below.
            .expect(2)
            .mount()
            .await;

        // Client with Matrix 1.12 that did not support extended profile fields yet.
        // However, because there is an `m.profile_fields` capability, we still rely on
        // it.
        let client =
            server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
        let capabilities_api = client.homeserver_capabilities();
        assert!(
            !capabilities_api
                .can_change_displayname()
                .await
                .expect("checking displayname capability failed")
        );
        assert!(
            !capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
        );
        assert!(
            !capabilities_api
                .extended_profile_fields()
                .await
                .expect("checking profile fields capability failed")
                .enabled
        );

        // Client with Matrix 1.16 that added support for extended profile fields, only
        // the `m.profile_fields` capability is used too.
        let client =
            server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
        let capabilities_api = client.homeserver_capabilities();
        assert!(
            !capabilities_api
                .can_change_displayname()
                .await
                .expect("checking displayname capability failed")
        );
        assert!(
            !capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
        );
        assert!(
            !capabilities_api
                .extended_profile_fields()
                .await
                .expect("checking profile fields capability failed")
                .enabled
        );
    }

    #[async_test]
    #[allow(deprecated)]
    async fn test_fine_grained_extended_profile_fields_capabilities() {
        let server = MatrixMockServer::new().await;

        // The user can only set the avatar URL.
        // The legacy capabilities say differently, but they will be ignored.
        let mut profile_fields = ProfileFieldsCapability::new(true);
        profile_fields.allowed = Some(vec![ProfileFieldName::AvatarUrl]);
        let mut capabilities = Capabilities::new();
        capabilities.profile_fields = Some(profile_fields);
        capabilities.set_displayname = SetDisplayNameCapability::new(true);
        capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
        server
            .mock_get_homeserver_capabilities()
            .ok_with_capabilities(capabilities)
            // It should be called once by each client below.
            .expect(2)
            .mount()
            .await;

        // Client with Matrix 1.12 that did not support extended profile fields yet.
        // However, because there is an `m.profile_fields` capability, we still rely on
        // it.
        let client =
            server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
        let capabilities_api = client.homeserver_capabilities();
        assert!(
            !capabilities_api
                .can_change_displayname()
                .await
                .expect("checking displayname capability failed")
        );
        assert!(
            capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
        );
        assert!(
            capabilities_api
                .extended_profile_fields()
                .await
                .expect("checking profile fields capability failed")
                .enabled
        );

        // Client with Matrix 1.16 that added support for extended profile fields, only
        // the `m.profile_fields` capability is used too.
        let client =
            server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
        let capabilities_api = client.homeserver_capabilities();
        assert!(
            !capabilities_api
                .can_change_displayname()
                .await
                .expect("checking displayname capability failed")
        );
        assert!(
            capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
        );
        assert!(
            capabilities_api
                .extended_profile_fields()
                .await
                .expect("checking profile fields capability failed")
                .enabled
        );
    }
}