From 1426ea4b2252f89807aaa3c5d02aeaa786fdca78 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 17 Mar 2026 11:19:34 +0000 Subject: [PATCH] Add support for locking to the mock homeserver and use in tests --- crates/handlers/src/admin/v1/users/lock.rs | 34 +++++++++++++++++- crates/handlers/src/admin/v1/users/unlock.rs | 36 +++++++++++++++++--- crates/matrix/src/mock.rs | 22 ++++++++++-- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/crates/handlers/src/admin/v1/users/lock.rs b/crates/handlers/src/admin/v1/users/lock.rs index e2ebfa574..d80cc6451 100644 --- a/crates/handlers/src/admin/v1/users/lock.rs +++ b/crates/handlers/src/admin/v1/users/lock.rs @@ -102,7 +102,11 @@ mod tests { use chrono::Duration; use hyper::{Request, StatusCode}; use mas_data_model::Clock; - use mas_storage::{RepositoryAccess, user::UserRepository}; + use mas_storage::{ + RepositoryAccess, + queue::{ProvisionUserJob, QueueJobRepositoryExt}, + user::UserRepository, + }; use sqlx::PgPool; use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup}; @@ -119,8 +123,25 @@ mod tests { .add(&mut state.rng(), &state.clock, "alice".to_owned()) .await .unwrap(); + + repo.queue_job() + .schedule_job(&mut state.rng(), &state.clock, ProvisionUserJob::new(&user)) + .await + .unwrap(); + repo.save().await.unwrap(); + state.run_jobs_in_queue().await; + assert!( + !state + .homeserver_connection + .query_user_raw("alice") + .await + .unwrap() + .locked, + "User should not be locked at start of test" + ); + let request = Request::post(format!("/api/admin/v1/users/{}/lock", user.id)) .bearer(&token) .empty(); @@ -133,6 +154,17 @@ mod tests { body["data"]["attributes"]["locked_at"], serde_json::json!(state.clock.now()) ); + + state.run_jobs_in_queue().await; + assert!( + state + .homeserver_connection + .query_user_raw("alice") + .await + .unwrap() + .locked, + "User should be locked" + ); } #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] diff --git a/crates/handlers/src/admin/v1/users/unlock.rs b/crates/handlers/src/admin/v1/users/unlock.rs index c14d0b5d3..d580ab27c 100644 --- a/crates/handlers/src/admin/v1/users/unlock.rs +++ b/crates/handlers/src/admin/v1/users/unlock.rs @@ -102,7 +102,11 @@ mod tests { use hyper::{Request, StatusCode}; use mas_data_model::Clock; use mas_matrix::{HomeserverConnection, ProvisionRequest}; - use mas_storage::{RepositoryAccess, user::UserRepository}; + use mas_storage::{ + RepositoryAccess, + queue::{ProvisionUserJob, QueueJobRepositoryExt}, + user::UserRepository, + }; use sqlx::PgPool; use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup}; @@ -120,16 +124,27 @@ mod tests { .await .unwrap(); let user = repo.user().lock(&state.clock, user).await.unwrap(); - repo.save().await.unwrap(); // Also provision the user on the homeserver, because this endpoint will try to // reactivate it - state - .homeserver_connection - .provision_user(&ProvisionRequest::new(&user.username, &user.sub, false)) + repo.queue_job() + .schedule_job(&mut state.rng(), &state.clock, ProvisionUserJob::new(&user)) .await .unwrap(); + repo.save().await.unwrap(); + + state.run_jobs_in_queue().await; + assert!( + state + .homeserver_connection + .query_user_raw("alice") + .await + .unwrap() + .locked, + "User should be locked at start of test" + ); + let request = Request::post(format!("/api/admin/v1/users/{}/unlock", user.id)) .bearer(&token) .empty(); @@ -141,6 +156,17 @@ mod tests { body["data"]["attributes"]["locked_at"], serde_json::Value::Null ); + + state.run_jobs_in_queue().await; + assert!( + !state + .homeserver_connection + .query_user_raw("alice") + .await + .unwrap() + .locked, + "User should not be locked" + ); } #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] diff --git a/crates/matrix/src/mock.rs b/crates/matrix/src/mock.rs index 1334d7e4f..2dbbf4b9a 100644 --- a/crates/matrix/src/mock.rs +++ b/crates/matrix/src/mock.rs @@ -10,9 +10,10 @@ use anyhow::Context; use async_trait::async_trait; use tokio::sync::RwLock; -use crate::{MatrixUser, ProvisionRequest}; +use crate::{HomeserverConnection as _, MatrixUser, ProvisionRequest}; -struct MockUser { +#[derive(Clone)] +pub struct MockUser { sub: String, avatar_url: Option, displayname: Option, @@ -20,6 +21,7 @@ struct MockUser { emails: Option>, cross_signing_reset_allowed: bool, deactivated: bool, + pub locked: bool, } /// A mock implementation of a [`HomeserverConnection`], which never fails and @@ -50,6 +52,18 @@ impl HomeserverConnection { pub async fn reserve_localpart(&self, localpart: &'static str) { self.reserved_localparts.write().await.insert(localpart); } + + /// Like `query_user` but get the raw test state of the user. + /// + /// # Errors + /// + /// Will fail if the user doesn't exist. + pub async fn query_user_raw(&self, localpart: &str) -> Result { + let mxid = self.mxid(localpart); + let users = self.users.read().await; + let user = users.get(&mxid).context("User not found")?; + Ok(user.clone()) + } } #[async_trait] @@ -85,6 +99,7 @@ impl crate::HomeserverConnection for HomeserverConnection { emails: None, cross_signing_reset_allowed: false, deactivated: false, + locked: false, }); anyhow::ensure!( @@ -104,6 +119,8 @@ impl crate::HomeserverConnection for HomeserverConnection { user.avatar_url = avatar_url.map(ToOwned::to_owned); }); + user.locked = request.locked(); + Ok(inserted) } @@ -219,7 +236,6 @@ impl crate::HomeserverConnection for HomeserverConnection { #[cfg(test)] mod tests { use super::*; - use crate::HomeserverConnection as _; #[tokio::test] async fn test_mock_connection() {