From e1aca8a1cb3e52ca487ac7710ff8a1f0ad2c8ca6 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 5 Nov 2026 08:08:17 +0100 Subject: [PATCH] Fix masked email query pagination Leave pagination parameters on the request for QueryResponseBuilder instead of extracting them for storage-level pagination. Add a focused masked-email query regression covering successive positions and duplicate page results. --- crates/jmap/src/inbuxa/masked_email.rs | 2 +- tests/src/system/masked_email.rs | 52 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/crates/jmap/src/inbuxa/masked_email.rs b/crates/jmap/src/inbuxa/masked_email.rs index 65afa73..5650e1f 100644 --- a/crates/jmap/src/inbuxa/masked_email.rs +++ b/crates/jmap/src/inbuxa/masked_email.rs @@ -291,7 +291,7 @@ pub(crate) async fn query(mut req: RegistryQueryResponse<'_>) -> trc::Result false, })?; req.request - .extract_parameters(req.server.core.jmap.query_max_results, Some(Property::Id))?; + .extract_parameters(req.server.core.jmap.query_max_results, None)?; // ME-19: one account's masks, or every mask for a server-level // administrator who asks for no account in particular diff --git a/tests/src/system/masked_email.rs b/tests/src/system/masked_email.rs index b307a66..4d33fe9 100644 --- a/tests/src/system/masked_email.rs +++ b/tests/src/system/masked_email.rs @@ -492,6 +492,58 @@ pub async fn masked_email_compat() { } } +/// `cargo test -p tests masked_email_query_pagination -- --ignored`. +#[ignore] +#[tokio::test(flavor = "multi_thread")] +pub async fn masked_email_query_pagination() { + let test = TestServerBuilder::new("masked_email_query_pagination") + .await + .with_default_listeners() + .await + .build() + .await; + let admin = test.create_admin_account("admin@example.org").await; + admin.find_or_create_domain("example.org").await; + let alice = admin + .create_user_account("alice@example.org", SECRET_ALICE, "Alice", &[], vec![]) + .await; + let mut ids = Vec::new(); + for prefix in ["first", "second", "third"] { + ids.push(alice.x_create(json!({"emailPrefix": prefix})).await); + } + ids.sort_unstable(); + + let mut seen = Vec::new(); + for (position, id) in ids.iter().enumerate() { + // calculateTotal must be false to exercise parameter extraction's + // storage-level pagination path. + let page = alice + .registry_query_paginated( + ObjectType::MaskedEmail, + "id", + true, + Some(position as i32), + Some(1), + None, + None, + false, + ) + .await; + let page_ids = page.object_ids().collect::>(); + assert!( + page_ids.iter().all(|id| !seen.contains(id)), + "a previous page's result was repeated" + ); + assert_eq!(page_ids, vec![*id], "page at position {position}"); + assert_eq!(page.method_response()["position"], json!(position)); + seen.extend(page_ids); + } + + if test.is_reset() { + test.temp_dir.delete(); + } +} + /// Runs the masked email tests alone: /// `cargo test -p tests masked_email_tests -- --ignored`. #[ignore] -- 2.53.0