mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-12-09 04:46:30 +08:00
Mailbox tests passing.
This commit is contained in:
parent
a3a0396772
commit
60fa410aa5
19 changed files with 334 additions and 526 deletions
|
|
@ -2,7 +2,7 @@ use std::{borrow::Cow, collections::HashSet};
|
|||
|
||||
use store::{
|
||||
fts::builder::ToTokens,
|
||||
write::{BatchBuilder, IntoOperations, Operation},
|
||||
write::{BatchBuilder, BitmapFamily, IntoOperations, Operation},
|
||||
Serialize, BM_TAG, HASH_EXACT,
|
||||
};
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ impl IntoOperations for ObjectIndexBuilder {
|
|||
}
|
||||
(Some(current), None) => {
|
||||
// Deletion
|
||||
build_batch(batch, self.index, ¤t, true);
|
||||
build_batch(batch, self.index, ¤t, false);
|
||||
batch.ops.push(Operation::Value {
|
||||
field: Property::Value.into(),
|
||||
family: 0,
|
||||
|
|
@ -149,190 +149,186 @@ fn merge_batch(
|
|||
if current_value == &value {
|
||||
continue;
|
||||
}
|
||||
match index
|
||||
.iter()
|
||||
.find_map(|i| {
|
||||
if i.property == property {
|
||||
Some(i.index_as)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_default()
|
||||
{
|
||||
IndexAs::Text { tokenize, index } => {
|
||||
// Remove current text from index
|
||||
let mut add_tokens = HashSet::new();
|
||||
let mut remove_tokens = HashSet::new();
|
||||
if let Some(text) = current_value.as_string() {
|
||||
if index {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: text.serialize(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if tokenize {
|
||||
remove_tokens = text.to_tokens();
|
||||
}
|
||||
}
|
||||
|
||||
// Add new text to index
|
||||
if let Some(text) = value.as_string() {
|
||||
if index {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: text.serialize(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
if tokenize {
|
||||
for token in text.to_tokens() {
|
||||
if !remove_tokens.remove(&token) {
|
||||
add_tokens.insert(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update tokens
|
||||
for (token, set) in [(add_tokens, true), (remove_tokens, false)] {
|
||||
for token in token {
|
||||
batch.ops.push(Operation::hash(
|
||||
&token,
|
||||
HASH_EXACT,
|
||||
property.clone().into(),
|
||||
set,
|
||||
));
|
||||
}
|
||||
}
|
||||
for index_property in index {
|
||||
if index_property.property != property {
|
||||
continue;
|
||||
}
|
||||
IndexAs::TextList { tokenize, index } => {
|
||||
let mut add_tokens = HashSet::new();
|
||||
let mut remove_tokens = HashSet::new();
|
||||
let mut add_values = HashSet::new();
|
||||
let mut remove_values = HashSet::new();
|
||||
match index_property.index_as {
|
||||
IndexAs::Text { tokenize, index } => {
|
||||
// Remove current text from index
|
||||
let mut add_tokens = HashSet::new();
|
||||
let mut remove_tokens = HashSet::new();
|
||||
if let Some(text) = current_value.as_string() {
|
||||
if index {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: text.serialize(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if tokenize {
|
||||
remove_tokens = text.to_tokens();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove current text from index
|
||||
if let Some(current_values) = current_value.as_list() {
|
||||
for current_value in current_values {
|
||||
if let Some(text) = current_value.as_string() {
|
||||
if index {
|
||||
remove_values.insert(text);
|
||||
}
|
||||
if tokenize {
|
||||
remove_tokens.extend(text.to_tokens());
|
||||
// Add new text to index
|
||||
if let Some(text) = value.as_string() {
|
||||
if index {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: text.serialize(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
if tokenize {
|
||||
for token in text.to_tokens() {
|
||||
if !remove_tokens.remove(&token) {
|
||||
add_tokens.insert(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add new text to index
|
||||
if let Some(values) = value.as_list() {
|
||||
for value in values {
|
||||
if let Some(text) = value.as_string() {
|
||||
if index && !remove_values.remove(text) {
|
||||
add_values.insert(text);
|
||||
// Update tokens
|
||||
for (token, set) in [(add_tokens, true), (remove_tokens, false)] {
|
||||
for token in token {
|
||||
batch.ops.push(Operation::hash(
|
||||
&token,
|
||||
HASH_EXACT,
|
||||
property.clone().into(),
|
||||
set,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
IndexAs::TextList { tokenize, index } => {
|
||||
let mut add_tokens = HashSet::new();
|
||||
let mut remove_tokens = HashSet::new();
|
||||
let mut add_values = HashSet::new();
|
||||
let mut remove_values = HashSet::new();
|
||||
|
||||
// Remove current text from index
|
||||
if let Some(current_values) = current_value.as_list() {
|
||||
for current_value in current_values {
|
||||
if let Some(text) = current_value.as_string() {
|
||||
if index {
|
||||
remove_values.insert(text);
|
||||
}
|
||||
if tokenize {
|
||||
remove_tokens.extend(text.to_tokens());
|
||||
}
|
||||
}
|
||||
if tokenize {
|
||||
for token in text.to_tokens() {
|
||||
if !remove_tokens.remove(&token) {
|
||||
add_tokens.insert(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new text to index
|
||||
if let Some(values) = value.as_list() {
|
||||
for value in values {
|
||||
if let Some(text) = value.as_string() {
|
||||
if index && !remove_values.remove(text) {
|
||||
add_values.insert(text);
|
||||
}
|
||||
if tokenize {
|
||||
for token in text.to_tokens() {
|
||||
if !remove_tokens.remove(&token) {
|
||||
add_tokens.insert(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update index
|
||||
for (values, set) in [(add_values, true), (remove_values, false)] {
|
||||
for value in values {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: value.serialize(),
|
||||
set,
|
||||
});
|
||||
// Update index
|
||||
for (values, set) in [(add_values, true), (remove_values, false)] {
|
||||
for value in values {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: value.serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update tokens
|
||||
for (token, set) in [(add_tokens, true), (remove_tokens, false)] {
|
||||
for token in token {
|
||||
batch.ops.push(Operation::hash(
|
||||
&token,
|
||||
HASH_EXACT,
|
||||
property.clone().into(),
|
||||
set,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
index_as @ (IndexAs::Integer | IndexAs::LongInteger) => {
|
||||
if let Some(current_value) = current_value.try_cast_uint() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: current_value.into_index(index_as),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if let Some(value) = value.try_cast_uint() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: value.into_index(index_as),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexAs::IntegerList => {
|
||||
let mut add_values = HashSet::new();
|
||||
let mut remove_values = HashSet::new();
|
||||
|
||||
if let Some(current_values) = current_value.as_list() {
|
||||
for current_value in current_values {
|
||||
if let Some(current_value) = current_value.try_cast_uint() {
|
||||
remove_values.insert(current_value);
|
||||
// Update tokens
|
||||
for (token, set) in [(add_tokens, true), (remove_tokens, false)] {
|
||||
for token in token {
|
||||
batch.ops.push(Operation::hash(
|
||||
&token,
|
||||
HASH_EXACT,
|
||||
property.clone().into(),
|
||||
set,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(values) = value.as_list() {
|
||||
for value in values {
|
||||
if let Some(value) = value.try_cast_uint() {
|
||||
if !remove_values.remove(&value) {
|
||||
add_values.insert(value);
|
||||
index_as @ (IndexAs::Integer | IndexAs::LongInteger) => {
|
||||
if let Some(current_value) = current_value.try_cast_uint() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: current_value.into_index(index_as),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if let Some(value) = value.try_cast_uint() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: value.into_index(index_as),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexAs::IntegerList => {
|
||||
let mut add_values = HashSet::new();
|
||||
let mut remove_values = HashSet::new();
|
||||
|
||||
if let Some(current_values) = current_value.as_list() {
|
||||
for current_value in current_values {
|
||||
if let Some(current_value) = current_value.try_cast_uint() {
|
||||
remove_values.insert(current_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(values) = value.as_list() {
|
||||
for value in values {
|
||||
if let Some(value) = value.try_cast_uint() {
|
||||
if !remove_values.remove(&value) {
|
||||
add_values.insert(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (values, set) in [(add_values, true), (remove_values, false)] {
|
||||
for value in values {
|
||||
batch.ops.push(Operation::Index {
|
||||
for (values, set) in [(add_values, true), (remove_values, false)] {
|
||||
for value in values {
|
||||
batch.ops.push(Operation::Index {
|
||||
field: property.clone().into(),
|
||||
key: (value as u32).serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
IndexAs::HasProperty => {
|
||||
if current_value == &Value::Null {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
family: ().family(),
|
||||
field: property.clone().into(),
|
||||
key: (value as u32).serialize(),
|
||||
set,
|
||||
key: vec![],
|
||||
set: true,
|
||||
});
|
||||
} else if value == Value::Null {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
family: ().family(),
|
||||
field: property.clone().into(),
|
||||
key: vec![],
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexAs::None => (),
|
||||
}
|
||||
IndexAs::HasProperty => {
|
||||
if current_value == &Value::Null {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
family: BM_TAG,
|
||||
field: property.clone().into(),
|
||||
key: vec![],
|
||||
set: true,
|
||||
});
|
||||
} else if value == Value::Null {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
family: BM_TAG,
|
||||
field: property.clone().into(),
|
||||
key: vec![],
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexAs::None => (),
|
||||
}
|
||||
if value != Value::Null {
|
||||
current.set(property, value);
|
||||
|
|
@ -373,7 +369,7 @@ fn build_batch(
|
|||
&token,
|
||||
HASH_EXACT,
|
||||
(&item.property).into(),
|
||||
true,
|
||||
set,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -403,7 +399,7 @@ fn build_batch(
|
|||
&token,
|
||||
HASH_EXACT,
|
||||
(&item.property).into(),
|
||||
true,
|
||||
set,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -440,7 +436,7 @@ fn build_batch(
|
|||
}
|
||||
(value, IndexAs::HasProperty) if value != &Value::Null => {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
family: BM_TAG,
|
||||
family: ().family(),
|
||||
field: (&item.property).into(),
|
||||
key: vec![],
|
||||
set,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use utils::map::vec_map::VecMap;
|
|||
|
||||
use crate::{
|
||||
error::{method::MethodError, set::SetError},
|
||||
method::set::SetResponse,
|
||||
object::Object,
|
||||
request::{
|
||||
reference::{MaybeReference, ResultReference},
|
||||
|
|
@ -305,52 +306,52 @@ impl Response {
|
|||
}
|
||||
}
|
||||
|
||||
impl Object<SetValue> {
|
||||
pub fn iterate_and_eval_references(
|
||||
self,
|
||||
response: &Response,
|
||||
) -> impl Iterator<Item = Result<(Property, MaybePatchValue), SetError>> + '_ {
|
||||
// Resolve id references, which were previously validated.
|
||||
// If the ID is not found it means that set failed for that id, so we return a setError
|
||||
// instead of failing the entire request with a MethodError::InvalidResultReference.
|
||||
self.properties
|
||||
.into_iter()
|
||||
.map(|(property, set_value)| match set_value {
|
||||
SetValue::Value(value) => Ok((property, MaybePatchValue::Value(value))),
|
||||
SetValue::Patch(patch) => Ok((property, MaybePatchValue::Patch(patch))),
|
||||
SetValue::IdReference(MaybeReference::Reference(id_ref)) => {
|
||||
if let Some(id) = response.created_ids.get(&id_ref) {
|
||||
Ok((property, MaybePatchValue::Value(Value::Id(*id))))
|
||||
} else {
|
||||
Err(SetError::not_found()
|
||||
.with_description(format!("Id reference {id_ref:?} not found.")))
|
||||
}
|
||||
impl SetResponse {
|
||||
pub fn eval_object_references(&self, set_value: SetValue) -> Result<MaybePatchValue, SetError> {
|
||||
match set_value {
|
||||
SetValue::Value(value) => Ok(MaybePatchValue::Value(value)),
|
||||
SetValue::Patch(patch) => Ok(MaybePatchValue::Patch(patch)),
|
||||
SetValue::IdReference(MaybeReference::Reference(id_ref)) => {
|
||||
if let Some(Value::Id(id)) = self
|
||||
.created
|
||||
.get(&id_ref)
|
||||
.and_then(|obj| obj.properties.get(&Property::Id))
|
||||
{
|
||||
Ok(MaybePatchValue::Value(Value::Id(*id)))
|
||||
} else {
|
||||
Err(SetError::not_found()
|
||||
.with_description(format!("Id reference {id_ref:?} not found.")))
|
||||
}
|
||||
SetValue::IdReference(MaybeReference::Value(id)) => {
|
||||
Ok((property, MaybePatchValue::Value(Value::Id(id))))
|
||||
}
|
||||
SetValue::IdReferences(id_refs) => {
|
||||
let mut ids = Vec::with_capacity(id_refs.len());
|
||||
for id_ref in id_refs {
|
||||
match id_ref {
|
||||
MaybeReference::Value(id) => {
|
||||
ids.push(Value::Id(id));
|
||||
}
|
||||
MaybeReference::Reference(id_ref) => {
|
||||
if let Some(id) = response.created_ids.get(&id_ref) {
|
||||
ids.push(Value::Id(*id));
|
||||
} else {
|
||||
return Err(SetError::not_found().with_description(format!(
|
||||
"Id reference {id_ref:?} not found."
|
||||
)));
|
||||
}
|
||||
}
|
||||
SetValue::IdReference(MaybeReference::Value(id)) => {
|
||||
Ok(MaybePatchValue::Value(Value::Id(id)))
|
||||
}
|
||||
SetValue::IdReferences(id_refs) => {
|
||||
let mut ids = Vec::with_capacity(id_refs.len());
|
||||
for id_ref in id_refs {
|
||||
match id_ref {
|
||||
MaybeReference::Value(id) => {
|
||||
ids.push(Value::Id(id));
|
||||
}
|
||||
MaybeReference::Reference(id_ref) => {
|
||||
if let Some(Value::Id(id)) = self
|
||||
.created
|
||||
.get(&id_ref)
|
||||
.and_then(|obj| obj.properties.get(&Property::Id))
|
||||
{
|
||||
ids.push(Value::Id(*id));
|
||||
} else {
|
||||
return Err(SetError::not_found().with_description(format!(
|
||||
"Id reference {id_ref:?} not found."
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((property, MaybePatchValue::Value(Value::List(ids))))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
Ok(MaybePatchValue::Value(Value::List(ids)))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ impl JMAP {
|
|||
("", &Method::POST) => {
|
||||
return match fetch_body(req, self.config.request_max_size).await {
|
||||
Ok(bytes) => {
|
||||
let delete = "fd";
|
||||
//let delete = "fd";
|
||||
//println!("<- {}", String::from_utf8_lossy(&bytes));
|
||||
|
||||
match self.handle_request(&bytes).await {
|
||||
|
|
|
|||
|
|
@ -53,11 +53,10 @@ impl JMAP {
|
|||
query::RequestArguments::Principal => todo!(),
|
||||
},
|
||||
RequestMethod::Set(mut req) => match req.take_arguments() {
|
||||
set::RequestArguments::Email => self.email_set(req, &response).await.into(),
|
||||
set::RequestArguments::Mailbox(arguments) => self
|
||||
.mailbox_set(req.with_arguments(arguments), &response)
|
||||
.await
|
||||
.into(),
|
||||
set::RequestArguments::Email => self.email_set(req).await.into(),
|
||||
set::RequestArguments::Mailbox(arguments) => {
|
||||
self.mailbox_set(req.with_arguments(arguments)).await.into()
|
||||
}
|
||||
set::RequestArguments::Identity => todo!(),
|
||||
set::RequestArguments::EmailSubmission(_) => todo!(),
|
||||
set::RequestArguments::PushSubscription => todo!(),
|
||||
|
|
@ -65,7 +64,7 @@ impl JMAP {
|
|||
set::RequestArguments::VacationResponse => todo!(),
|
||||
set::RequestArguments::Principal => todo!(),
|
||||
},
|
||||
RequestMethod::Changes(_) => todo!(),
|
||||
RequestMethod::Changes(req) => self.changes(req).await.into(),
|
||||
RequestMethod::Copy(_) => todo!(),
|
||||
RequestMethod::CopyBlob(_) => todo!(),
|
||||
RequestMethod::ImportEmail(req) => self.email_import(req).await.into(),
|
||||
|
|
|
|||
|
|
@ -47,8 +47,7 @@ impl JMAP {
|
|||
);
|
||||
continue;
|
||||
}
|
||||
let enable = "true";
|
||||
/*for mailbox_id in &mailbox_ids {
|
||||
for mailbox_id in &mailbox_ids {
|
||||
if !valid_mailbox_ids.contains(*mailbox_id) {
|
||||
not_created.append(
|
||||
id,
|
||||
|
|
@ -58,7 +57,7 @@ impl JMAP {
|
|||
);
|
||||
continue 'outer;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
// Fetch raw message to import
|
||||
let raw_message = match self.blob_download(&email.blob_id, account_id).await {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ use jmap_proto::{
|
|||
},
|
||||
method::set::{RequestArguments, SetRequest, SetResponse},
|
||||
object::Object,
|
||||
response::Response,
|
||||
types::{
|
||||
collection::Collection,
|
||||
id::Id,
|
||||
|
|
@ -46,7 +45,6 @@ impl JMAP {
|
|||
pub async fn email_set(
|
||||
&self,
|
||||
mut request: SetRequest<RequestArguments>,
|
||||
response: &Response,
|
||||
) -> Result<SetResponse, MethodError> {
|
||||
// Prepare response
|
||||
let account_id = request.account_id.document_id();
|
||||
|
|
@ -60,13 +58,6 @@ impl JMAP {
|
|||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let remove = "fdf";
|
||||
mailbox_ids.insert(0);
|
||||
mailbox_ids.insert(1);
|
||||
mailbox_ids.insert(2);
|
||||
mailbox_ids.insert(3);
|
||||
mailbox_ids.insert(4);
|
||||
|
||||
let will_destroy = request.unwrap_destroy();
|
||||
|
||||
// Process creates
|
||||
|
|
@ -107,15 +98,15 @@ impl JMAP {
|
|||
let mut size_attachments = 0;
|
||||
|
||||
// Parse properties
|
||||
for item in object.iterate_and_eval_references(response) {
|
||||
let item = match item {
|
||||
Ok(item) => item,
|
||||
for (property, value) in object.properties {
|
||||
let value = match set_response.eval_object_references(value) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
set_response.not_created.append(id, err);
|
||||
continue 'create;
|
||||
}
|
||||
};
|
||||
match item {
|
||||
match (property, value) {
|
||||
(Property::MailboxIds, MaybePatchValue::Value(Value::List(ids))) => {
|
||||
mailboxes = ids
|
||||
.into_iter()
|
||||
|
|
@ -736,15 +727,15 @@ impl JMAP {
|
|||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Email);
|
||||
|
||||
for item in object.iterate_and_eval_references(response) {
|
||||
let item = match item {
|
||||
Ok(item) => item,
|
||||
for (property, value) in object.properties {
|
||||
let value = match set_response.eval_object_references(value) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
set_response.not_updated.append(id, err);
|
||||
continue 'update;
|
||||
}
|
||||
};
|
||||
match item {
|
||||
match (property, value) {
|
||||
(Property::MailboxIds, MaybePatchValue::Value(Value::List(ids))) => {
|
||||
mailboxes.set(
|
||||
ids.into_iter()
|
||||
|
|
@ -899,9 +890,7 @@ impl JMAP {
|
|||
set_response.destroyed.push(destroy_id);
|
||||
}
|
||||
Err(err) => {
|
||||
set_response
|
||||
.not_destroyed
|
||||
.append(destroy_id, SetError::not_found());
|
||||
set_response.not_destroyed.append(destroy_id, err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -181,6 +181,9 @@ impl JMAP {
|
|||
|
||||
mailbox.append(property.clone(), value);
|
||||
}
|
||||
|
||||
// Add result to response
|
||||
response.list.push(mailbox);
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,11 +47,7 @@ impl JMAP {
|
|||
}
|
||||
Filter::Role(role) => {
|
||||
if let Some(role) = role {
|
||||
filters.push(query::Filter::has_text(
|
||||
Property::Role,
|
||||
&role,
|
||||
Language::None,
|
||||
));
|
||||
filters.push(query::Filter::eq(Property::Role, role));
|
||||
} else {
|
||||
filters.push(query::Filter::Not);
|
||||
filters.push(query::Filter::is_in_bitmap(Property::Role, ()));
|
||||
|
|
@ -170,7 +166,7 @@ impl JMAP {
|
|||
for comparator in request
|
||||
.sort
|
||||
.and_then(|s| if !s.is_empty() { s.into() } else { None })
|
||||
.unwrap_or_else(|| vec![Comparator::descending(SortProperty::ParentId)])
|
||||
.unwrap_or_else(|| vec![Comparator::ascending(SortProperty::ParentId)])
|
||||
{
|
||||
comparators.push(match comparator.property {
|
||||
SortProperty::Name => {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ use jmap_proto::{
|
|||
mailbox::SetArguments,
|
||||
Object,
|
||||
},
|
||||
response::Response,
|
||||
types::{
|
||||
collection::Collection,
|
||||
id::Id,
|
||||
|
|
@ -27,10 +26,10 @@ use crate::JMAP;
|
|||
|
||||
use super::{INBOX_ID, TRASH_ID};
|
||||
|
||||
struct SetContext<'x> {
|
||||
struct SetContext {
|
||||
account_id: u32,
|
||||
primary_id: u32,
|
||||
response: &'x Response,
|
||||
set_response: SetResponse,
|
||||
mailbox_ids: RoaringBitmap,
|
||||
will_destroy: Vec<Id>,
|
||||
}
|
||||
|
|
@ -42,12 +41,10 @@ static SCHEMA: &[IndexProperty] = &[
|
|||
index: true,
|
||||
})
|
||||
.required(),
|
||||
IndexProperty::new(Property::Role)
|
||||
.index_as(IndexAs::Text {
|
||||
tokenize: false,
|
||||
index: true,
|
||||
})
|
||||
.required(),
|
||||
IndexProperty::new(Property::Role).index_as(IndexAs::Text {
|
||||
tokenize: false,
|
||||
index: true,
|
||||
}),
|
||||
IndexProperty::new(Property::Role).index_as(IndexAs::HasProperty),
|
||||
IndexProperty::new(Property::ParentId).index_as(IndexAs::Integer),
|
||||
IndexProperty::new(Property::SortOrder).index_as(IndexAs::Integer),
|
||||
|
|
@ -59,18 +56,16 @@ impl JMAP {
|
|||
pub async fn mailbox_set(
|
||||
&self,
|
||||
mut request: SetRequest<SetArguments>,
|
||||
response: &Response,
|
||||
) -> Result<SetResponse, MethodError> {
|
||||
// Prepare response
|
||||
let account_id = request.account_id.document_id();
|
||||
let mut set_response = self
|
||||
.prepare_set_response(&request, Collection::Mailbox)
|
||||
.await?;
|
||||
let on_destroy_remove_emails = request.arguments.on_destroy_remove_emails.unwrap_or(false);
|
||||
let mut ctx = SetContext {
|
||||
account_id,
|
||||
primary_id: account_id,
|
||||
response,
|
||||
set_response: self
|
||||
.prepare_set_response(&request, Collection::Mailbox)
|
||||
.await?,
|
||||
mailbox_ids: self
|
||||
.get_document_ids(account_id, Collection::Mailbox)
|
||||
.await?
|
||||
|
|
@ -104,10 +99,10 @@ impl JMAP {
|
|||
MethodError::ServerPartialFail
|
||||
})?;
|
||||
|
||||
set_response.created(id, document_id);
|
||||
ctx.set_response.created(id, document_id);
|
||||
}
|
||||
Err(err) => {
|
||||
set_response.not_created.append(id, err);
|
||||
ctx.set_response.not_created.append(id, err);
|
||||
continue 'create;
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +138,7 @@ impl JMAP {
|
|||
match self.store.write(batch.build()).await {
|
||||
Ok(_) => (),
|
||||
Err(store::Error::AssertValueFailed) => {
|
||||
set_response.not_updated.append(id, SetError::forbidden().with_description(
|
||||
ctx.set_response.not_updated.append(id, SetError::forbidden().with_description(
|
||||
"Another process modified this mailbox, please try again.",
|
||||
));
|
||||
continue 'update;
|
||||
|
|
@ -159,15 +154,17 @@ impl JMAP {
|
|||
}
|
||||
}
|
||||
}
|
||||
set_response.updated.append(id, None);
|
||||
ctx.set_response.updated.append(id, None);
|
||||
}
|
||||
Err(err) => {
|
||||
set_response.not_updated.append(id, err);
|
||||
ctx.set_response.not_updated.append(id, err);
|
||||
continue 'update;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
set_response.not_updated.append(id, SetError::not_found());
|
||||
ctx.set_response
|
||||
.not_updated
|
||||
.append(id, SetError::not_found());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -175,8 +172,9 @@ impl JMAP {
|
|||
'destroy: for id in ctx.will_destroy {
|
||||
let document_id = id.document_id();
|
||||
// Internal folders cannot be deleted
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
if document_id == INBOX_ID || document_id == TRASH_ID {
|
||||
set_response.not_destroyed.append(
|
||||
ctx.set_response.not_destroyed.append(
|
||||
id,
|
||||
SetError::forbidden()
|
||||
.with_description("You are not allowed to delete Inbox or Trash folders."),
|
||||
|
|
@ -195,7 +193,7 @@ impl JMAP {
|
|||
.results
|
||||
.is_empty()
|
||||
{
|
||||
set_response.not_destroyed.append(
|
||||
ctx.set_response.not_destroyed.append(
|
||||
id,
|
||||
SetError::new(SetErrorType::MailboxHasChild)
|
||||
.with_description("Mailbox has at least one children."),
|
||||
|
|
@ -262,7 +260,7 @@ impl JMAP {
|
|||
Id::from_parts(thread_id, message_id),
|
||||
),
|
||||
Err(store::Error::AssertValueFailed) => {
|
||||
set_response.not_destroyed.append(
|
||||
ctx.set_response.not_destroyed.append(
|
||||
id,
|
||||
SetError::forbidden().with_description(
|
||||
concat!("Another process modified a message in this mailbox ",
|
||||
|
|
@ -296,7 +294,7 @@ impl JMAP {
|
|||
} else {
|
||||
// Delete message
|
||||
if let Ok(mut change) =
|
||||
self.email_delete(account_id, document_id).await?
|
||||
self.email_delete(account_id, message_id).await?
|
||||
{
|
||||
change.changes.remove(&(Collection::Mailbox as u8));
|
||||
changes.merge(change);
|
||||
|
|
@ -314,7 +312,7 @@ impl JMAP {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
set_response.not_destroyed.append(
|
||||
ctx.set_response.not_destroyed.append(
|
||||
id,
|
||||
SetError::new(SetErrorType::MailboxHasEmail)
|
||||
.with_description("Mailbox is not empty."),
|
||||
|
|
@ -342,9 +340,12 @@ impl JMAP {
|
|||
.custom(ObjectIndexBuilder::new(SCHEMA).with_current(mailbox.inner));
|
||||
|
||||
match self.store.write(batch.build()).await {
|
||||
Ok(_) => changes.log_delete(Collection::Mailbox, document_id),
|
||||
Ok(_) => {
|
||||
changes.log_delete(Collection::Mailbox, document_id);
|
||||
ctx.set_response.destroyed.push(id);
|
||||
}
|
||||
Err(store::Error::AssertValueFailed) => {
|
||||
set_response.not_destroyed.append(
|
||||
ctx.set_response.not_destroyed.append(
|
||||
id,
|
||||
SetError::forbidden().with_description(concat!(
|
||||
"Another process modified this mailbox ",
|
||||
|
|
@ -364,16 +365,18 @@ impl JMAP {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
set_response.not_destroyed.append(id, SetError::not_found());
|
||||
ctx.set_response
|
||||
.not_destroyed
|
||||
.append(id, SetError::not_found());
|
||||
}
|
||||
}
|
||||
|
||||
// Write changes
|
||||
if !changes.is_empty() {
|
||||
set_response.new_state = self.commit_changes(account_id, changes).await?.into();
|
||||
ctx.set_response.new_state = self.commit_changes(account_id, changes).await?.into();
|
||||
}
|
||||
|
||||
Ok(set_response)
|
||||
Ok(ctx.set_response)
|
||||
}
|
||||
|
||||
#[allow(clippy::blocks_in_if_conditions)]
|
||||
|
|
@ -381,22 +384,22 @@ impl JMAP {
|
|||
&self,
|
||||
changes_: Object<SetValue>,
|
||||
update: Option<(u32, Object<Value>)>,
|
||||
ctx: &SetContext<'_>,
|
||||
ctx: &SetContext,
|
||||
) -> Result<Result<ObjectIndexBuilder, SetError>, MethodError> {
|
||||
// Parse properties
|
||||
let mut changes = Object::with_capacity(changes_.properties.len());
|
||||
for item in changes_.iterate_and_eval_references(ctx.response) {
|
||||
let item = match item {
|
||||
Ok(item) => item,
|
||||
for (property, value) in changes_.properties {
|
||||
let value = match ctx.set_response.eval_object_references(value) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return Ok(Err(err));
|
||||
}
|
||||
};
|
||||
match item {
|
||||
let value = match (&property, value) {
|
||||
(Property::Name, MaybePatchValue::Value(Value::Text(value))) => {
|
||||
let value = value.trim();
|
||||
if !value.is_empty() && value.len() < self.config.mailbox_name_max_len {
|
||||
changes.append(Property::Name, Value::Text(value.to_string()));
|
||||
Value::Text(value.to_string())
|
||||
} else {
|
||||
return Ok(Err(SetError::invalid_properties()
|
||||
.with_property(Property::Name)
|
||||
|
|
@ -420,11 +423,9 @@ impl JMAP {
|
|||
.with_description("Parent ID does not exist.")));
|
||||
}
|
||||
|
||||
changes.append(Property::ParentId, Value::Id((parent_id + 1).into()));
|
||||
}
|
||||
(Property::ParentId, MaybePatchValue::Value(Value::Null)) => {
|
||||
changes.append(Property::ParentId, Value::Id(0u64.into()))
|
||||
Value::Id((parent_id + 1).into())
|
||||
}
|
||||
(Property::ParentId, MaybePatchValue::Value(Value::Null)) => Value::Id(0u64.into()),
|
||||
(Property::IsSubscribed, MaybePatchValue::Value(Value::Bool(subscribe))) => {
|
||||
let fixme = "true";
|
||||
let account_id = Value::Id(ctx.primary_id.into());
|
||||
|
|
@ -459,16 +460,14 @@ impl JMAP {
|
|||
}
|
||||
}
|
||||
}
|
||||
changes.append(
|
||||
Property::IsSubscribed,
|
||||
if let Some(new_value) = new_value {
|
||||
new_value
|
||||
} else if subscribe {
|
||||
Value::List(vec![account_id])
|
||||
} else {
|
||||
continue;
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(new_value) = new_value {
|
||||
new_value
|
||||
} else if subscribe {
|
||||
Value::List(vec![account_id])
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
(Property::Role, MaybePatchValue::Value(Value::Text(value))) => {
|
||||
let role = value.trim().to_lowercase();
|
||||
|
|
@ -477,28 +476,28 @@ impl JMAP {
|
|||
]
|
||||
.contains(&role.as_str())
|
||||
{
|
||||
changes.append(Property::Role, Value::Text(role));
|
||||
Value::Text(role)
|
||||
} else {
|
||||
return Ok(Err(SetError::invalid_properties()
|
||||
.with_property(Property::Role)
|
||||
.with_description(format!("Invalid role {role:?}."))));
|
||||
}
|
||||
}
|
||||
(Property::Role, MaybePatchValue::Value(Value::Null)) => {
|
||||
changes.append(Property::Role, Value::Null)
|
||||
}
|
||||
(Property::Role, MaybePatchValue::Value(Value::Null)) => Value::Null,
|
||||
(Property::SortOrder, MaybePatchValue::Value(Value::UnsignedInt(value))) => {
|
||||
changes.append(Property::SortOrder, Value::UnsignedInt(value));
|
||||
Value::UnsignedInt(value)
|
||||
}
|
||||
(Property::Acl, _) => {
|
||||
todo!()
|
||||
}
|
||||
(property, _) => {
|
||||
_ => {
|
||||
return Ok(Err(SetError::invalid_properties()
|
||||
.with_property(property)
|
||||
.with_description("Invalid property or value.".to_string())))
|
||||
}
|
||||
};
|
||||
|
||||
changes.append(property, value);
|
||||
}
|
||||
|
||||
// Validate depth and circular parent-child relationship
|
||||
|
|
|
|||
|
|
@ -354,6 +354,7 @@ impl Store {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
pub async fn destroy(&self) {
|
||||
let trx = self.db.create_trx().unwrap();
|
||||
trx.clear_range(&[0u8], &[u8::MAX]);
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ impl Store {
|
|||
trx.prepare_cached("INSERT OR REPLACE INTO i (k) VALUES (?)")?
|
||||
.execute([&key])?;
|
||||
} else {
|
||||
trx.prepare_cached("DELETE FROM v WHERE k = ?")?
|
||||
trx.prepare_cached("DELETE FROM i WHERE k = ?")?
|
||||
.execute([&key])?;
|
||||
}
|
||||
}
|
||||
|
|
@ -230,6 +230,7 @@ impl Store {
|
|||
.await
|
||||
}
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
pub async fn destroy(&self) {
|
||||
use crate::{
|
||||
SUBSPACE_ACLS, SUBSPACE_BITMAPS, SUBSPACE_BLOBS, SUBSPACE_INDEXES, SUBSPACE_LOGS,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ impl Store {
|
|||
move |changelog, key, value| {
|
||||
let change_id =
|
||||
key.deserialize_be_u64(key.len() - std::mem::size_of::<u64>())?;
|
||||
if !is_inclusive || change_id != from_change_id {
|
||||
if is_inclusive || change_id != from_change_id {
|
||||
if changelog.changes.is_empty() {
|
||||
changelog.from_change_id = change_id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use roaring::RoaringBitmap;
|
|||
use crate::{
|
||||
fts::{lang::LanguageDetector, Language},
|
||||
write::BitmapFamily,
|
||||
BitmapKey, Serialize, BM_DOCUMENT_IDS,
|
||||
BitmapKey, Serialize, Store, BM_DOCUMENT_IDS,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -235,3 +235,10 @@ impl BitmapKey<&'static [u8]> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
impl Store {
|
||||
pub async fn assert_is_empty(&self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
* for more details.
|
||||
*/
|
||||
|
||||
use std::{fs, path::PathBuf, sync::Arc, time::Instant};
|
||||
use std::{fs, path::PathBuf, sync::Arc};
|
||||
|
||||
use jmap::JMAP;
|
||||
use jmap_client::{
|
||||
|
|
@ -41,14 +41,12 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
|||
test_dir.push("resources");
|
||||
test_dir.push("jmap_mail_get");
|
||||
|
||||
let coco1 = "implement";
|
||||
let mailbox_id = "a".to_string();
|
||||
/*let mailbox_id = client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("JMAP Get", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();*/
|
||||
let mailbox_id = client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("JMAP Get", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
|
||||
for file_name in fs::read_dir(&test_dir).unwrap() {
|
||||
let mut file_name = file_name.as_ref().unwrap().path();
|
||||
|
|
@ -182,10 +180,9 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
|||
}
|
||||
}
|
||||
|
||||
let coco = "implement";
|
||||
//client.mailbox_destroy(&mailbox_id, true).await.unwrap();
|
||||
client.mailbox_destroy(&mailbox_id, true).await.unwrap();
|
||||
|
||||
//server.store.assert_is_empty();
|
||||
server.store.assert_is_empty().await;
|
||||
}
|
||||
|
||||
pub fn all_headers() -> Vec<email::Property> {
|
||||
|
|
|
|||
|
|
@ -77,8 +77,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client, insert: bool) {
|
|||
query_options(client).await;
|
||||
|
||||
println!("Deleting all messages...");
|
||||
let implement = "fds";
|
||||
/*let mut request = client.build();
|
||||
let mut request = client.build();
|
||||
let result_ref = request.query_email().result_reference();
|
||||
request.set_email().destroy_ref(result_ref);
|
||||
let response = request.send().await.unwrap();
|
||||
|
|
@ -89,7 +88,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client, insert: bool) {
|
|||
.unwrap_set_email()
|
||||
.unwrap();
|
||||
|
||||
server.store.assert_is_empty();*/
|
||||
server.store.assert_is_empty().await;
|
||||
}
|
||||
|
||||
pub async fn query(client: &mut Client) {
|
||||
|
|
|
|||
|
|
@ -38,22 +38,19 @@ use super::{find_values, replace_blob_ids, replace_boundaries, replace_values};
|
|||
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
println!("Running Email Set tests...");
|
||||
|
||||
let mailbox_id = "a";
|
||||
let coco = "fix";
|
||||
/*client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("JMAP Set", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();*/
|
||||
let mailbox_id = client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("JMAP Set", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
|
||||
create(client, &mailbox_id).await;
|
||||
update(client, &mailbox_id).await;
|
||||
|
||||
let coco = "fd";
|
||||
//client.mailbox_destroy(&mailbox_id, true).await.unwrap();
|
||||
client.mailbox_destroy(&mailbox_id, true).await.unwrap();
|
||||
|
||||
//server.store.assert_is_empty();
|
||||
server.store.assert_is_empty().await;
|
||||
}
|
||||
|
||||
async fn create(client: &mut Client, mailbox_id: &str) {
|
||||
|
|
@ -194,21 +191,18 @@ async fn update(client: &mut Client, root_mailbox_id: &str) {
|
|||
.unwrap();
|
||||
|
||||
// Create two test mailboxes
|
||||
let fix = "true";
|
||||
let test_mailbox1_id = "c".to_string();
|
||||
/*client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("Test 1", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();*/
|
||||
let test_mailbox2_id = "d".to_string();
|
||||
/*client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("Test 2", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();*/
|
||||
let test_mailbox1_id = client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("Test 1", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
let test_mailbox2_id = client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("Test 2", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
|
||||
// Set keywords and mailboxes
|
||||
let mut request = client.build();
|
||||
|
|
@ -309,15 +303,14 @@ async fn update(client: &mut Client, root_mailbox_id: &str) {
|
|||
assert_eq!(request.send_get_email().await.unwrap().not_found().len(), 2);
|
||||
|
||||
// Destroy test mailboxes
|
||||
let fix = "true";
|
||||
/*client
|
||||
client
|
||||
.mailbox_destroy(&test_mailbox1_id, true)
|
||||
.await
|
||||
.unwrap();
|
||||
client
|
||||
.mailbox_destroy(&test_mailbox2_id, true)
|
||||
.await
|
||||
.unwrap();*/
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn assert_email_properties(
|
||||
|
|
|
|||
|
|
@ -611,8 +611,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
|||
client.mailbox_destroy(&id, true).await.unwrap();
|
||||
}
|
||||
|
||||
let todo = "yes";
|
||||
//server.store.assert_is_empty();
|
||||
server.store.assert_is_empty().await;
|
||||
}
|
||||
|
||||
async fn create_test_mailboxes(client: &mut Client) -> AHashMap<String, String> {
|
||||
|
|
|
|||
|
|
@ -7,14 +7,12 @@ use jmap_proto::types::id::Id;
|
|||
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
println!("Running Email Thread tests...");
|
||||
|
||||
let mailbox_id = "a".to_string();
|
||||
let implementer = "fd";
|
||||
/*let mailbox_id = client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("JMAP Get", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();*/
|
||||
let mailbox_id = client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("JMAP Get", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
|
||||
let mut expected_result = vec!["".to_string(); 5];
|
||||
let mut thread_id = "".to_string();
|
||||
|
|
@ -43,8 +41,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
|||
expected_result
|
||||
);
|
||||
|
||||
let implement = "fd";
|
||||
//client.mailbox_destroy(&mailbox_id, true).await.unwrap();
|
||||
client.mailbox_destroy(&mailbox_id, true).await.unwrap();
|
||||
|
||||
//server.store.assert_is_empty();
|
||||
server.store.assert_is_empty().await;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ use store::ahash::{AHashMap, AHashSet};
|
|||
|
||||
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
println!("Running Email Merge Threads tests...");
|
||||
|
||||
simple_test(client).await;
|
||||
|
||||
let mut all_mailboxes = AHashMap::default();
|
||||
|
||||
for (base_test_num, test) in [test_1(), test_2(), test_3()].iter().enumerate() {
|
||||
|
|
@ -23,16 +20,14 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
|||
let mut mailbox_ids = Vec::with_capacity(6);
|
||||
|
||||
for test_num in 0..=5 {
|
||||
mailbox_ids.push(char::from(b'a' + test_num as u8).to_string());
|
||||
let coco = "fd";
|
||||
/*mailbox_ids.push(
|
||||
mailbox_ids.push(
|
||||
client
|
||||
.set_default_account_id(Id::new((base_test_num + test_num) as u64).to_string())
|
||||
.mailbox_create("Thread nightmare", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id(),
|
||||
);*/
|
||||
);
|
||||
}
|
||||
|
||||
for message in &messages {
|
||||
|
|
@ -177,8 +172,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
|||
}
|
||||
|
||||
// Delete all messages and make sure no keys are left in the store.
|
||||
let implement = "fdf";
|
||||
/*for (base_test_num, mailbox_ids) in all_mailboxes {
|
||||
for (base_test_num, mailbox_ids) in all_mailboxes {
|
||||
for (test_num, mailbox_id) in mailbox_ids.into_iter().enumerate() {
|
||||
client
|
||||
.set_default_account_id(Id::new((base_test_num + test_num) as u64).to_string())
|
||||
|
|
@ -188,169 +182,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
|||
}
|
||||
}
|
||||
|
||||
server.store.assert_is_empty();*/
|
||||
}
|
||||
|
||||
async fn simple_test(client: &mut Client) {
|
||||
let coco = "fdf";
|
||||
let mailbox_id = "a".to_string();
|
||||
/*
|
||||
let mailbox_id = client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.mailbox_create("JMAP Get", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();*/
|
||||
|
||||
// A simple thread that uses in-reply-to to link messages together
|
||||
let thread_1 = vec![
|
||||
client
|
||||
.email_import(
|
||||
"Message-ID: <t1-msg1>
|
||||
From: test1@example.com
|
||||
To: test2@example.com
|
||||
Subject: my thread
|
||||
|
||||
message here!"
|
||||
.into(),
|
||||
[&mailbox_id],
|
||||
None::<Vec<String>>,
|
||||
Some(1),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
client
|
||||
.email_import(
|
||||
"Message-ID: <t1-msg2>
|
||||
From: test2@example.com
|
||||
To: test1@example.com
|
||||
In-Reply-To: <t1-msg1>
|
||||
Subject: Re: my thread
|
||||
|
||||
reply here!"
|
||||
.into(),
|
||||
[&mailbox_id],
|
||||
None::<Vec<String>>,
|
||||
Some(2),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
client
|
||||
.email_import(
|
||||
"Message-ID: <t1-msg3>
|
||||
From: test1@example.com
|
||||
To: test2@example.com
|
||||
In-Reply-To: <t1-msg2>
|
||||
Subject: Re: my thread
|
||||
|
||||
last reply"
|
||||
.into(),
|
||||
[&mailbox_id],
|
||||
None::<Vec<String>>,
|
||||
Some(3),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
// Another simple thread, but this time with a shared reference header instead
|
||||
let thread_2 = vec![
|
||||
client
|
||||
.email_import(
|
||||
"Message-ID: <t2-msg1>
|
||||
From: test1@example.com
|
||||
To: test2@example.com
|
||||
Subject: my thread
|
||||
|
||||
message here!"
|
||||
.into(),
|
||||
[&mailbox_id],
|
||||
None::<Vec<String>>,
|
||||
Some(1),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
client
|
||||
.email_import(
|
||||
"Message-ID: <t2-msg2>
|
||||
References: <t2-msg1>
|
||||
From: test2@example.com
|
||||
To: test1@example.com
|
||||
Subject: my thread
|
||||
|
||||
reply here!"
|
||||
.into(),
|
||||
[&mailbox_id],
|
||||
None::<Vec<String>>,
|
||||
Some(2),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
client
|
||||
.email_import(
|
||||
"Message-ID: <t2-msg3>
|
||||
References: <t2-msg1>
|
||||
From: test1@example.com
|
||||
To: test2@example.com
|
||||
Subject: my thread
|
||||
|
||||
reply here!"
|
||||
.into(),
|
||||
[&mailbox_id],
|
||||
None::<Vec<String>>,
|
||||
Some(3),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
// Make sure none of the separate threads end up with the same thread ID
|
||||
assert_ne!(
|
||||
thread_1.first().unwrap().thread_id().unwrap(),
|
||||
thread_2.first().unwrap().thread_id().unwrap(),
|
||||
"Making sure thread 1 and thread 2 have different thread IDs"
|
||||
);
|
||||
|
||||
// Make sure each message in each thread ends up with the right thread ID
|
||||
assert_thread_ids_match(client, &thread_1, "thread chained with In-Reply-To header").await;
|
||||
assert_thread_ids_match(client, &thread_2, "thread with References header").await;
|
||||
|
||||
//client.mailbox_destroy(&mailbox_id, true).await.unwrap();
|
||||
}
|
||||
|
||||
async fn assert_thread_ids_match(
|
||||
client: &mut Client,
|
||||
emails: &Vec<jmap_client::email::Email>,
|
||||
description: &str,
|
||||
) {
|
||||
let thread_id = emails.first().unwrap().thread_id().unwrap();
|
||||
|
||||
// First, make sure the thread ID is the same for all messages in the thread
|
||||
for email in emails {
|
||||
assert_eq!(
|
||||
email.thread_id().unwrap(),
|
||||
thread_id,
|
||||
"Comparing thread IDs of messages in: {}",
|
||||
description
|
||||
);
|
||||
}
|
||||
|
||||
// Next, make sure querying the thread yields the same messages
|
||||
let full_thread = client.thread_get(thread_id).await.unwrap().unwrap();
|
||||
let mut email_ids_in_fetched_thread = full_thread.email_ids().to_vec();
|
||||
email_ids_in_fetched_thread.sort();
|
||||
|
||||
let mut expected_email_ids = emails
|
||||
.iter()
|
||||
.map(|email| email.id().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
expected_email_ids.sort();
|
||||
|
||||
assert_eq!(
|
||||
email_ids_in_fetched_thread, expected_email_ids,
|
||||
"Comparing email IDs in: {}",
|
||||
description
|
||||
);
|
||||
server.store.assert_is_empty().await;
|
||||
}
|
||||
|
||||
fn build_message(message: usize, in_reply_to: Option<usize>, thread_num: usize) -> String {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue