ACL storage optimization

This commit is contained in:
mdecimus 2023-12-21 13:29:09 +01:00
parent be78a26193
commit 31719963c9
10 changed files with 319 additions and 269 deletions

View file

@ -40,8 +40,12 @@ use jmap_proto::{
error::method::MethodError,
object::{index::ObjectIndexBuilder, Object},
types::{
acl::Acl, collection::Collection, id::Id, property::Property, state::StateChange,
type_state::DataType, value::Value,
acl::Acl,
collection::Collection,
property::Property,
state::StateChange,
type_state::DataType,
value::{AclGrant, Value},
},
};
use store::write::{assert::HashedValue, log::ChangeLogBuilder, BatchBuilder};
@ -65,64 +69,58 @@ impl<T: AsyncRead> Session<T> {
.inner
.properties
.get(&Property::Acl)
.and_then(|v| v.as_list())
.and_then(|v| v.as_acl())
{
for item in acls.chunks_exact(2) {
if let (
Some(Value::Id(id)),
Some(Value::UnsignedInt(acl_bits)),
) = (item.first(), item.last())
for item in acls {
if let Some(account_name) = data
.jmap
.directory
.query(QueryBy::Id(item.account_id), false)
.await
.unwrap_or_default()
.map(|p| p.name)
{
if let Some(account_name) = data
.jmap
.directory
.query(QueryBy::Id(id.document_id()), false)
.await
.unwrap_or_default()
.map(|p| p.name)
{
let mut rights = Vec::new();
let mut rights = Vec::new();
for acl in Bitmap::<Acl>::from(*acl_bits) {
match acl {
Acl::Read => {
rights.push(Rights::Lookup);
}
Acl::Modify => {
rights.push(Rights::CreateMailbox);
}
Acl::Delete => {
rights.push(Rights::DeleteMailbox);
}
Acl::ReadItems => {
rights.push(Rights::Read);
}
Acl::AddItems => {
rights.push(Rights::Insert);
}
Acl::ModifyItems => {
rights.push(Rights::Write);
rights.push(Rights::Seen);
}
Acl::RemoveItems => {
rights.push(Rights::DeleteMessages);
rights.push(Rights::Expunge);
}
Acl::CreateChild => {
rights.push(Rights::CreateMailbox);
}
Acl::Administer => {
rights.push(Rights::Administer);
}
Acl::Submit => {
rights.push(Rights::Post);
}
Acl::None => (),
for acl in item.grants {
match acl {
Acl::Read => {
rights.push(Rights::Lookup);
}
Acl::Modify => {
rights.push(Rights::CreateMailbox);
}
Acl::Delete => {
rights.push(Rights::DeleteMailbox);
}
Acl::ReadItems => {
rights.push(Rights::Read);
}
Acl::AddItems => {
rights.push(Rights::Insert);
}
Acl::ModifyItems => {
rights.push(Rights::Write);
rights.push(Rights::Seen);
}
Acl::RemoveItems => {
rights.push(Rights::DeleteMessages);
rights.push(Rights::Expunge);
}
Acl::CreateChild => {
rights.push(Rights::CreateMailbox);
}
Acl::Administer => {
rights.push(Rights::Administer);
}
Acl::Submit => {
rights.push(Rights::Post);
}
Acl::None => (),
}
permissions.push((account_name, rights));
}
permissions.push((account_name, rights));
}
}
}
@ -245,13 +243,13 @@ impl<T: AsyncRead> Session<T> {
};
// Obtain principal id
let (acl_account_id, id) = match data
let acl_account_id = match data
.jmap
.directory
.query(QueryBy::Name(arguments.identifier.as_ref().unwrap()), false)
.await
{
Ok(Some(principal)) => (principal.id, Value::Id(Id::from(principal.id))),
Ok(Some(principal)) => principal.id,
Ok(None) => {
data.write_bytes(
StatusResponse::no("Account does not exist")
@ -283,7 +281,7 @@ impl<T: AsyncRead> Session<T> {
)
})
.unwrap_or_else(|| (ModRightsOp::Replace, Bitmap::new()));
let acl = if let Value::List(acl) =
let acl = if let Value::Acl(acl) =
changes
.properties
.get_mut_or_insert_with(Property::Acl, || {
@ -292,7 +290,7 @@ impl<T: AsyncRead> Session<T> {
.properties
.get(&Property::Acl)
.cloned()
.unwrap_or_else(|| Value::List(Vec::new()))
.unwrap_or_else(|| Value::Acl(Vec::new()))
}) {
acl
} else {
@ -305,48 +303,37 @@ impl<T: AsyncRead> Session<T> {
return;
};
if let Some(idx) = acl.iter().position(|item| item == &id) {
if let Some(item) = acl
.iter_mut()
.find(|item| item.account_id == acl_account_id)
{
match op {
ModRightsOp::Replace => {
if !rights.is_empty() {
acl[idx + 1] = Value::UnsignedInt(rights.into());
item.grants = rights;
} else {
acl.remove(idx);
acl.remove(idx);
acl.retain(|item| item.account_id != acl_account_id);
}
}
ModRightsOp::Add | ModRightsOp::Remove => {
if let Some(Value::UnsignedInt(current)) = acl.get_mut(idx + 1) {
let mut bitmap = Bitmap::from(*current);
if op == ModRightsOp::Add {
bitmap.union(&rights);
} else {
for right in rights {
bitmap.remove(right);
}
}
if !bitmap.is_empty() {
*current = bitmap.into();
} else {
acl.remove(idx);
acl.remove(idx);
}
} else {
data.write_bytes(
StatusResponse::database_failure()
.with_tag(arguments.tag)
.into_bytes(),
)
.await;
return;
ModRightsOp::Add => {
item.grants.union(&rights);
}
ModRightsOp::Remove => {
for right in rights {
item.grants.remove(right);
}
if item.grants.is_empty() {
acl.retain(|item| item.account_id != acl_account_id);
}
}
}
} else if !rights.is_empty() {
match op {
ModRightsOp::Add | ModRightsOp::Replace => {
acl.push(id);
acl.push(Value::UnsignedInt(rights.into()));
acl.push(AclGrant {
account_id: acl_account_id,
grants: rights,
});
}
ModRightsOp::Remove => (),
}

View file

@ -376,64 +376,51 @@ fn merge_batch(
}
IndexAs::Acl => {
match (current_value, &value) {
(Value::List(current_value), Value::List(value)) => {
(Value::Acl(current_value), Value::Acl(value)) => {
// Remove deleted ACLs
for item in current_value.chunks_exact(2) {
if let Some(Value::Id(id)) = item.first() {
if !value.contains(&Value::Id(*id)) {
batch.ops.push(Operation::acl(id.document_id(), None));
}
for current_item in current_value {
if !value
.iter()
.any(|item| item.account_id == current_item.account_id)
{
batch
.ops
.push(Operation::acl(current_item.account_id, None));
}
}
// Update ACLs
for item in value.chunks_exact(2) {
if let (Some(Value::Id(id)), Some(Value::UnsignedInt(acl))) =
(item.first(), item.last())
{
let mut add_item = true;
for current_item in current_value.chunks_exact(2) {
if let (
Some(Value::Id(current_id)),
Some(Value::UnsignedInt(current_acl)),
) = (current_item.first(), current_item.last())
{
if id == current_id {
if acl == current_acl {
add_item = false;
}
break;
}
for item in value {
let mut add_item = true;
for current_item in current_value {
if item.account_id == current_item.account_id {
if item.grants == current_item.grants {
add_item = false;
}
}
if add_item {
batch.ops.push(Operation::acl(
id.document_id(),
acl.serialize().into(),
));
break;
}
}
}
}
(Value::Null, Value::List(values)) => {
// Add all ACLs
for item in values.chunks_exact(2) {
if let (Some(Value::Id(id)), Some(Value::UnsignedInt(acl))) =
(item.first(), item.last())
{
if add_item {
batch.ops.push(Operation::acl(
id.document_id(),
acl.serialize().into(),
item.account_id,
item.grants.bitmap.serialize().into(),
));
}
}
}
(Value::List(current_values), Value::Null) => {
(Value::Null, Value::Acl(values)) => {
// Add all ACLs
for item in values {
batch.ops.push(Operation::acl(
item.account_id,
item.grants.bitmap.serialize().into(),
));
}
}
(Value::Acl(current_values), Value::Null) => {
// Remove all ACLs
for item in current_values.chunks_exact(2) {
if let Some(Value::Id(id)) = item.first() {
batch.ops.push(Operation::acl(id.document_id(), None));
}
for item in current_values {
batch.ops.push(Operation::acl(item.account_id, None));
}
}
_ => {}
@ -556,16 +543,16 @@ fn build_batch(
});
}
}
(Value::List(values), IndexAs::Acl) => {
for item in values.chunks_exact(2) {
if let (Some(Value::Id(id)), Some(Value::UnsignedInt(acl))) =
(item.first(), item.last())
{
batch.ops.push(Operation::acl(
id.document_id(),
if set { acl.serialize().into() } else { None },
));
}
(Value::Acl(values), IndexAs::Acl) => {
for item in values {
batch.ops.push(Operation::acl(
item.account_id,
if set {
item.grants.bitmap.serialize().into()
} else {
None
},
));
}
}
(value, IndexAs::HasProperty) if value != &Value::Null => {

View file

@ -32,15 +32,20 @@ use std::slice::Iter;
use store::{
write::{DeserializeFrom, SerializeInto, ToBitmaps},
Deserialize, Serialize,
Deserialize, Serialize, U64_LEN,
};
use utils::{
codec::leb128::{Leb128Iterator, Leb128Vec},
map::vec_map::VecMap,
map::{bitmap::Bitmap, vec_map::VecMap},
};
use crate::types::{
blob::BlobId, date::UTCDate, id::Id, keyword::Keyword, property::Property, value::Value,
blob::BlobId,
date::UTCDate,
id::Id,
keyword::Keyword,
property::Property,
value::{AclGrant, Value},
};
#[derive(Debug, Clone, Default, serde::Serialize, PartialEq, Eq)]
@ -122,7 +127,8 @@ const BLOB: u8 = 7;
const KEYWORD: u8 = 8;
const LIST: u8 = 9;
const OBJECT: u8 = 10;
const NULL: u8 = 11;
const ACL: u8 = 11;
const NULL: u8 = 12;
impl Serialize for Value {
fn serialize(self) -> Vec<u8> {
@ -169,6 +175,29 @@ impl SerializeInto for Object<Value> {
}
}
}
impl SerializeInto for AclGrant {
fn serialize_into(&self, buf: &mut Vec<u8>) {
buf.push_leb128(self.account_id);
buf.extend_from_slice(self.grants.bitmap.to_be_bytes().as_slice());
}
}
impl DeserializeFrom for AclGrant {
fn deserialize_from(bytes: &mut Iter<'_, u8>) -> Option<Self> {
let account_id = bytes.next_leb128()?;
let mut grants = [0u8; U64_LEN];
for byte in grants.iter_mut() {
*byte = *bytes.next()?;
}
Some(Self {
account_id,
grants: Bitmap::from(u64::from_be_bytes(grants)),
})
}
}
impl DeserializeFrom for Object<Value> {
fn deserialize_from(bytes: &mut Iter<'_, u8>) -> Option<Object<Value>> {
let len = bytes.next_leb128()?;
@ -227,6 +256,13 @@ impl SerializeInto for Value {
buf.push(BLOB);
v.serialize_into(buf);
}
Value::Acl(v) => {
buf.push(ACL);
buf.push_leb128(v.len());
for i in v {
i.serialize_into(buf);
}
}
Value::Null => {
buf.push(NULL);
}
@ -257,6 +293,14 @@ impl DeserializeFrom for Value {
}
OBJECT => Some(Value::Object(Object::deserialize_from(bytes)?)),
BLOB => Some(Value::Blob(Vec::deserialize_from(bytes)?)),
ACL => {
let len = bytes.next_leb128()?;
let mut items = Vec::with_capacity(len);
for _ in 0..len {
items.push(AclGrant::deserialize_from(bytes)?);
}
Some(Value::Acl(items))
}
NULL => Some(Value::Null),
_ => None,
}

View file

@ -25,6 +25,7 @@ use std::{borrow::Cow, fmt::Display};
use mail_parser::{Addr, DateTime, Group};
use serde::Serialize;
use utils::map::bitmap::Bitmap;
use crate::{
object::Object,
@ -33,6 +34,7 @@ use crate::{
};
use super::{
acl::Acl,
any_id::AnyId,
blob::BlobId,
date::UTCDate,
@ -53,11 +55,18 @@ pub enum Value {
Keyword(Keyword),
List(Vec<Value>),
Object(Object<Value>),
Acl(Vec<AclGrant>),
Blob(Vec<u8>),
#[default]
Null,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
pub struct AclGrant {
pub account_id: u32,
pub grants: Bitmap<Acl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SetValue {
Value(Value),
@ -282,6 +291,13 @@ impl Value {
}
}
pub fn as_acl(&self) -> Option<&Vec<AclGrant>> {
match self {
Value::Acl(l) => Some(l),
_ => None,
}
}
pub fn as_uint(&self) -> Option<u64> {
match self {
Value::UnsignedInt(u) => Some(*u),

View file

@ -29,7 +29,7 @@ use jmap_proto::{
acl::Acl,
collection::Collection,
property::Property,
value::{MaybePatchValue, Value},
value::{AclGrant, MaybePatchValue, Value},
},
};
use store::{
@ -272,14 +272,13 @@ impl JMAP {
) -> Result<(), SetError> {
match acl_changes {
MaybePatchValue::Value(Value::List(values)) => {
changes.properties.set(
Property::Acl,
Value::List(self.map_acl_accounts(values).await?),
);
changes
.properties
.set(Property::Acl, Value::Acl(self.map_acl_set(values).await?));
}
MaybePatchValue::Patch(patch) => {
let patch = self.map_acl_accounts(patch).await?;
let acl = if let Value::List(acl) =
let (mut patch, is_update) = self.map_acl_patch(patch).await?;
let acl = if let Value::Acl(acl) =
changes
.properties
.get_mut_or_insert_with(Property::Acl, || {
@ -287,7 +286,7 @@ impl JMAP {
.and_then(|current| {
current.inner.properties.get(&Property::Acl).cloned()
})
.unwrap_or_else(|| Value::List(Vec::new()))
.unwrap_or_else(|| Value::Acl(Vec::new()))
}) {
acl
} else {
@ -295,56 +294,37 @@ impl JMAP {
.with_property(Property::Acl)
.with_description("Invalid ACL value found."));
};
let account_id = patch.first().unwrap().as_id().unwrap();
match patch.len() {
2 => {
let acl_update = patch.last().unwrap().as_uint().unwrap();
if let Some(idx) =
acl.iter().position(|item| item.as_id() == Some(account_id))
if let Some(is_set) = is_update {
if !patch.grants.is_empty() {
if let Some(acl_item) = acl
.iter_mut()
.find(|item| item.account_id == patch.account_id)
{
if acl_update != 0 {
acl[idx + 1] = Value::UnsignedInt(acl_update);
} else if acl.len() > 2 {
acl.remove(idx);
acl.remove(idx);
let item = patch.grants.pop().unwrap();
if is_set {
acl_item.grants.insert(item);
} else {
acl.clear();
acl_item.grants.remove(item);
if acl_item.grants.is_empty() {
acl.retain(|item| item.account_id != patch.account_id);
}
}
} else if acl_update != 0 {
acl.push(Value::Id(*account_id));
acl.push(Value::UnsignedInt(acl_update));
} else if is_set {
acl.push(patch);
}
}
3 => {
let acl_item = Acl::from(patch[1].as_uint().unwrap());
let set = patch[2].as_bool().unwrap_or(false);
if let Some(idx) =
acl.iter().position(|item| item.as_id() == Some(account_id))
{
if let Some(Value::UnsignedInt(current)) = acl.get_mut(idx + 1) {
let mut bitmap = Bitmap::from(*current);
if set {
bitmap.insert(acl_item);
} else {
bitmap.remove(acl_item);
}
if !bitmap.is_empty() {
*current = bitmap.into();
} else {
acl.remove(idx);
acl.remove(idx);
}
} else {
return Err(SetError::invalid_properties()
.with_property(Property::Acl)
.with_description("Invalid ACL value found."));
}
} else if set {
acl.push(Value::Id(*account_id));
acl.push(Value::UnsignedInt(Bitmap::new().with_item(acl_item).into()));
}
} else if !patch.grants.is_empty() {
if let Some(acl_item) = acl
.iter_mut()
.find(|item| item.account_id == patch.account_id)
{
acl_item.grants = patch.grants;
} else {
acl.push(patch);
}
_ => unreachable!(),
} else {
acl.retain(|item| item.account_id != patch.account_id);
}
}
_ => {
@ -358,38 +338,29 @@ impl JMAP {
pub async fn acl_get(
&self,
value: &[Value],
value: &[AclGrant],
access_token: &AccessToken,
account_id: u32,
) -> Value {
if access_token.is_member(account_id)
|| value.chunks_exact(2).any(|item| {
access_token.is_member(
item.first()
.and_then(|v| v.as_id().map(|id| id.document_id()))
.unwrap_or(u32::MAX),
) && Bitmap::from(item.last().and_then(|a| a.as_uint()).unwrap_or_default())
.contains(Acl::Administer)
|| value.iter().any(|item| {
access_token.is_member(item.account_id) && item.grants.contains(Acl::Administer)
})
{
let mut acl_obj = Object::with_capacity(value.len() / 2);
for item in value.chunks_exact(2) {
if let (Some(Value::Id(id)), Some(Value::UnsignedInt(acl_bits))) =
(item.first(), item.last())
for item in value {
if let Some(principal) = self
.directory
.query(QueryBy::Id(item.account_id), false)
.await
.unwrap_or_default()
{
if let Some(principal) = self
.directory
.query(QueryBy::Id(id.document_id()), false)
.await
.unwrap_or_default()
{
acl_obj.append(
Property::_T(principal.name),
Bitmap::<Acl>::from(*acl_bits)
.map(|acl_item| Value::Text(acl_item.to_string()))
.collect::<Vec<_>>(),
);
}
acl_obj.append(
Property::_T(principal.name),
item.grants
.map(|acl_item| Value::Text(acl_item.to_string()))
.collect::<Vec<_>>(),
);
}
}
@ -404,61 +375,59 @@ impl JMAP {
changes: &Object<Value>,
current: &Option<HashedValue<Object<Value>>>,
) {
if let Value::List(acl_changes) = changes.get(&Property::Acl) {
if let Value::Acl(acl_changes) = changes.get(&Property::Acl) {
let access_tokens = &self.access_tokens;
if let Some(Value::List(acl_current)) = current
if let Some(Value::Acl(acl_current)) = current
.as_ref()
.and_then(|current| current.inner.properties.get(&Property::Acl))
{
for current_item in acl_current.chunks_exact(2) {
for current_item in acl_current {
let mut invalidate = true;
for change_item in acl_changes.chunks_exact(2) {
if change_item.first() == current_item.first() {
invalidate = change_item.last() != current_item.last();
for change_item in acl_changes {
if change_item.account_id == current_item.account_id {
invalidate = change_item.grants != current_item.grants;
break;
}
}
if invalidate {
if let Some(Value::Id(id)) = current_item.first() {
access_tokens.remove(&id.document_id());
}
access_tokens.remove(&current_item.account_id);
}
}
for change_item in acl_changes.chunks_exact(2) {
for change_item in acl_changes {
let mut invalidate = true;
for current_item in acl_current.chunks_exact(2) {
if change_item.first() == current_item.first() {
invalidate = change_item.last() != current_item.last();
for current_item in acl_current {
if change_item.account_id == current_item.account_id {
invalidate = change_item.grants != current_item.grants;
break;
}
}
if invalidate {
if let Some(Value::Id(id)) = change_item.first() {
access_tokens.remove(&id.document_id());
}
access_tokens.remove(&change_item.account_id);
}
}
} else {
for value in acl_changes {
if let Value::Id(id) = value {
access_tokens.remove(&id.document_id());
}
access_tokens.remove(&value.account_id);
}
}
}
}
async fn map_acl_accounts(&self, mut acl_set: Vec<Value>) -> Result<Vec<Value>, SetError> {
for item in &mut acl_set {
if let Value::Text(account_name) = item {
async fn map_acl_set(&self, acl_set: Vec<Value>) -> Result<Vec<AclGrant>, SetError> {
let mut acls = Vec::with_capacity(acl_set.len() / 2);
for item in acl_set.chunks_exact(2) {
if let (Value::Text(account_name), Value::UnsignedInt(grants)) = (&item[0], &item[1]) {
match self
.directory
.query(QueryBy::Name(account_name), false)
.await
{
Ok(Some(principal)) => {
*item = Value::Id(principal.id.into());
acls.push(AclGrant {
account_id: principal.id,
grants: Bitmap::from(*grants),
});
}
Ok(None) => {
return Err(SetError::invalid_properties()
@ -471,10 +440,47 @@ impl JMAP {
.with_description("Temporary server failure during lookup"));
}
}
} else {
return Err(SetError::invalid_properties()
.with_property(Property::Acl)
.with_description("Invalid ACL value found."));
}
}
Ok(acl_set)
Ok(acls)
}
async fn map_acl_patch(
&self,
acl_patch: Vec<Value>,
) -> Result<(AclGrant, Option<bool>), SetError> {
if let (Value::Text(account_name), Value::UnsignedInt(grants)) =
(&acl_patch[0], &acl_patch[1])
{
match self
.directory
.query(QueryBy::Name(account_name), false)
.await
{
Ok(Some(principal)) => Ok((
AclGrant {
account_id: principal.id,
grants: Bitmap::from(*grants),
},
acl_patch.get(2).map(|v| v.as_bool().unwrap_or(false)),
)),
Ok(None) => Err(SetError::invalid_properties()
.with_property(Property::Acl)
.with_description(format!("Account {account_name} does not exist."))),
_ => Err(SetError::forbidden()
.with_property(Property::Acl)
.with_description("Temporary server failure during lookup")),
}
} else {
Err(SetError::invalid_properties()
.with_property(Property::Acl)
.with_description("Invalid ACL value found."))
}
}
}
@ -485,14 +491,10 @@ pub trait EffectiveAcl {
impl EffectiveAcl for Object<Value> {
fn effective_acl(&self, access_token: &AccessToken) -> Bitmap<Acl> {
let mut acl = Bitmap::<Acl>::new();
if let Some(Value::List(permissions)) = self.properties.get(&Property::Acl) {
for item in permissions.chunks(2) {
if let (Some(Value::Id(account_id)), Some(Value::UnsignedInt(acl_bits))) =
(item.first(), item.last())
{
if access_token.is_member(account_id.document_id()) {
acl.union(&Bitmap::from(*acl_bits));
}
if let Some(Value::Acl(permissions)) = self.properties.get(&Property::Acl) {
for item in permissions {
if access_token.is_member(item.account_id) {
acl.union(&item.grants);
}
}
}

View file

@ -181,6 +181,14 @@ impl JMAP {
.results
.is_empty()
{
tracing::debug!(
context = "email_ingest",
event = "skip",
account_id = ?params.account_id,
from = ?message.from(),
message_id = message_id,
"Duplicate message skipped.");
return Ok(IngestedEmail {
id: Id::default(),
change_id: u64::MAX,

View file

@ -233,7 +233,7 @@ impl JMAP {
values
.properties
.get(&Property::Acl)
.and_then(|v| v.as_list())
.and_then(|v| v.as_acl())
.map(|v| &v[..])
.unwrap_or_else(|| &[]),
access_token,

View file

@ -128,7 +128,7 @@ pub async fn test(params: &mut JMAPTest) {
);
assert!(client.session().account(&account_id).unwrap().is_personal());
// Uploads up to 50000000 bytes should be allowed
// Uploads up to 5000000 bytes should be allowed
assert_eq!(
client
.upload(None, vec![b'A'; 5000000], None)

View file

@ -190,7 +190,7 @@ max-objects = 100000
max-concurrent = 8
[jmap.protocol.upload]
max-size = 50000000
max-size = 5000000
max-concurrent = 4
ttl = "1m"
@ -289,30 +289,30 @@ pub async fn jmap_tests() {
//assert_is_empty(params.server.clone()).await;
let coco = 1;
//email_query::test(&mut params, delete).await;
/*email_get::test(&mut params).await;
/*email_query::test(&mut params, delete).await;
email_get::test(&mut params).await;
email_set::test(&mut params).await;
email_parse::test(&mut params).await;
email_search_snippet::test(&mut params).await;
email_changes::test(&mut params).await;
email_query_changes::test(&mut params).await;
email_copy::test(&mut params).await;
thread_get::test(&mut params).await;*/
//thread_merge::test(&mut params).await;
//mailbox::test(&mut params).await;
//delivery::test(&mut params).await;
/*auth_acl::test(&mut params).await;
thread_get::test(&mut params).await;
thread_merge::test(&mut params).await;
mailbox::test(&mut params).await;
delivery::test(&mut params).await;
auth_acl::test(&mut params).await;*/
auth_limits::test(&mut params).await;
auth_oauth::test(&mut params).await;
event_source::test(&mut params).await;
push_subscription::test(&mut params).await;
sieve_script::test(&mut params).await;
vacation_response::test(&mut params).await;*/
vacation_response::test(&mut params).await;
email_submission::test(&mut params).await;
/*websocket::test(&mut params).await;
websocket::test(&mut params).await;
quota::test(&mut params).await;
crypto::test(&mut params).await;
blob::test(&mut params).await;*/
blob::test(&mut params).await;
if delete {
params.temp_dir.delete();

View file

@ -29,7 +29,7 @@ use crate::{
};
use jmap::{email::ingest::IngestEmail, IngestError};
use jmap_client::{email, mailbox::Role};
use jmap_proto::types::{collection::Collection, id::Id, property::Property};
use jmap_proto::types::{collection::Collection, id::Id};
use mail_parser::{mailbox::mbox::MessageIterator, MessageParser};
use store::{
ahash::{AHashMap, AHashSet},
@ -236,6 +236,12 @@ async fn test_multi_thread(params: &mut JMAPTest) {
.await
.unwrap()
.take_id();
params
.client
.set_default_account_id(Id::new(0u64).to_string())
.mailbox_create("Other mailbox", None::<String>, Role::None)
.await
.unwrap();
let mailbox_id = Id::from_bytes(mailbox_id_str.as_bytes())
.unwrap()
.document_id();
@ -294,7 +300,7 @@ async fn test_multi_thread(params: &mut JMAPTest) {
messages as u64,
params
.server
.get_tag(0, Collection::Email, Property::MailboxIds, mailbox_id,)
.get_document_ids(0, Collection::Email)
.await
.unwrap()
.unwrap()