291 lines
10 KiB
Rust
291 lines
10 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use crate::jmap::{
|
|
JMAPTest, assert_is_empty,
|
|
mail::{
|
|
changes::{LogAction, ParseState},
|
|
mailbox::destroy_all_mailboxes,
|
|
},
|
|
};
|
|
use ::email::message::metadata::MessageData;
|
|
use common::storage::index::ObjectIndexBuilder;
|
|
use jmap_client::{
|
|
core::query::{Comparator, Filter},
|
|
email,
|
|
mailbox::Role,
|
|
};
|
|
use jmap_proto::types::state::State;
|
|
use std::str::FromStr;
|
|
use store::{
|
|
ahash::{AHashMap, AHashSet},
|
|
write::BatchBuilder,
|
|
};
|
|
use types::{
|
|
collection::{Collection, SyncCollection},
|
|
id::Id,
|
|
};
|
|
|
|
pub async fn test(params: &mut JMAPTest) {
|
|
println!("Running Email QueryChanges tests...");
|
|
let server = params.server.clone();
|
|
let mailbox1_id = params
|
|
.client
|
|
.set_default_account_id(Id::new(1).to_string())
|
|
.mailbox_create("JMAP Changes 1", None::<String>, Role::None)
|
|
.await
|
|
.unwrap()
|
|
.take_id();
|
|
let mailbox2_id = params
|
|
.client
|
|
.mailbox_create("JMAP Changes 2", None::<String>, Role::None)
|
|
.await
|
|
.unwrap()
|
|
.take_id();
|
|
|
|
let mut states = vec![State::Initial];
|
|
let mut id_map = AHashMap::default();
|
|
|
|
let mut updated_ids = AHashSet::default();
|
|
let mut removed_ids = AHashSet::default();
|
|
let mut type1_ids = AHashSet::default();
|
|
|
|
let mut thread_id = 100;
|
|
|
|
for (change_num, change) in [
|
|
LogAction::Insert(0),
|
|
LogAction::Insert(1),
|
|
LogAction::Insert(2),
|
|
LogAction::Move(0, 3),
|
|
LogAction::Insert(4),
|
|
LogAction::Insert(5),
|
|
LogAction::Update(1),
|
|
LogAction::Update(2),
|
|
LogAction::Delete(1),
|
|
LogAction::Insert(6),
|
|
LogAction::Insert(7),
|
|
LogAction::Update(2),
|
|
LogAction::Update(4),
|
|
LogAction::Update(5),
|
|
LogAction::Update(6),
|
|
LogAction::Update(7),
|
|
LogAction::Delete(4),
|
|
LogAction::Delete(5),
|
|
LogAction::Delete(6),
|
|
LogAction::Insert(8),
|
|
LogAction::Insert(9),
|
|
LogAction::Insert(10),
|
|
LogAction::Update(3),
|
|
LogAction::Update(2),
|
|
LogAction::Update(8),
|
|
LogAction::Move(9, 11),
|
|
LogAction::Move(10, 12),
|
|
LogAction::Delete(8),
|
|
]
|
|
.iter()
|
|
.enumerate()
|
|
{
|
|
match &change {
|
|
LogAction::Insert(id) => {
|
|
let jmap_id = Id::from_str(
|
|
params
|
|
.client
|
|
.email_import(
|
|
format!(
|
|
"From: test_{}\nSubject: test_{}\n\ntest",
|
|
if change_num % 2 == 0 { 1 } else { 2 },
|
|
*id
|
|
)
|
|
.into_bytes(),
|
|
[if change_num % 2 == 0 {
|
|
&mailbox1_id
|
|
} else {
|
|
&mailbox2_id
|
|
}],
|
|
[if change_num % 2 == 0 { "1" } else { "2" }].into(),
|
|
Some(*id as i64),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.id()
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
id_map.insert(*id, jmap_id);
|
|
if change_num % 2 == 0 {
|
|
type1_ids.insert(jmap_id);
|
|
}
|
|
}
|
|
LogAction::Update(id) => {
|
|
let id = *id_map.get(id).unwrap();
|
|
let mut batch = BatchBuilder::new();
|
|
batch
|
|
.update_document(id.document_id())
|
|
.log_item_update(SyncCollection::Email, id.prefix_id().into());
|
|
server.store().write(batch.build_all()).await.unwrap();
|
|
updated_ids.insert(id);
|
|
}
|
|
LogAction::Delete(id) => {
|
|
let id = *id_map.get(id).unwrap();
|
|
params.client.email_destroy(&id.to_string()).await.unwrap();
|
|
removed_ids.insert(id);
|
|
}
|
|
LogAction::Move(from, to) => {
|
|
let id = *id_map.get(from).unwrap();
|
|
let new_id = Id::from_parts(thread_id, id.document_id());
|
|
|
|
//let new_thread_id = store::rand::random::<u32>();
|
|
|
|
let old_message_ = server
|
|
.get_archive(1, Collection::Email, id.document_id())
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
let old_message = old_message_.to_unarchived::<MessageData>().unwrap();
|
|
let mut new_message = old_message.deserialize::<MessageData>().unwrap();
|
|
new_message.thread_id = thread_id;
|
|
|
|
server
|
|
.core
|
|
.storage
|
|
.data
|
|
.write(
|
|
BatchBuilder::new()
|
|
.with_account_id(1)
|
|
.with_collection(Collection::Email)
|
|
.update_document(id.document_id())
|
|
.custom(
|
|
ObjectIndexBuilder::new()
|
|
.with_current(old_message)
|
|
.with_changes(new_message),
|
|
)
|
|
.unwrap()
|
|
.build_all(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
id_map.insert(*to, new_id);
|
|
if type1_ids.contains(&id) {
|
|
type1_ids.insert(new_id);
|
|
}
|
|
removed_ids.insert(id);
|
|
thread_id += 1;
|
|
}
|
|
LogAction::UpdateChild(_) => unreachable!(),
|
|
}
|
|
|
|
let mut new_state = State::Initial;
|
|
for state in &states {
|
|
for (test_num, query) in vec![
|
|
QueryChanges {
|
|
filter: None,
|
|
sort: vec![email::query::Comparator::received_at()],
|
|
since_query_state: state.clone(),
|
|
max_changes: 0,
|
|
up_to_id: None,
|
|
},
|
|
QueryChanges {
|
|
filter: Some(email::query::Filter::from("test_1").into()),
|
|
sort: vec![email::query::Comparator::received_at()],
|
|
since_query_state: state.clone(),
|
|
max_changes: 0,
|
|
up_to_id: None,
|
|
},
|
|
QueryChanges {
|
|
filter: Some(email::query::Filter::in_mailbox(&mailbox1_id).into()),
|
|
sort: vec![email::query::Comparator::received_at()],
|
|
since_query_state: state.clone(),
|
|
max_changes: 0,
|
|
up_to_id: None,
|
|
},
|
|
QueryChanges {
|
|
filter: None,
|
|
sort: vec![email::query::Comparator::received_at()],
|
|
since_query_state: state.clone(),
|
|
max_changes: 0,
|
|
up_to_id: id_map
|
|
.get(&7)
|
|
.map(|id| id.to_string().into())
|
|
.unwrap_or(None),
|
|
},
|
|
]
|
|
.into_iter()
|
|
.enumerate()
|
|
{
|
|
if test_num == 3 && query.up_to_id.is_none() {
|
|
continue;
|
|
}
|
|
let mut request = params.client.build();
|
|
let query_request = request
|
|
.query_email_changes(query.since_query_state.to_string())
|
|
.sort(query.sort);
|
|
|
|
if let Some(filter) = query.filter {
|
|
query_request.filter(filter);
|
|
}
|
|
|
|
if let Some(up_to_id) = query.up_to_id {
|
|
query_request.up_to_id(up_to_id);
|
|
}
|
|
|
|
let changes = request.send_query_email_changes().await.unwrap();
|
|
|
|
if test_num == 0 || test_num == 1 {
|
|
// Immutable filters should not return modified ids, only deletions.
|
|
for id in changes.removed() {
|
|
let id = Id::from_str(id).unwrap();
|
|
assert!(
|
|
removed_ids.contains(&id),
|
|
"{:?} (id: {:?})",
|
|
changes,
|
|
id_map.iter().find(|(_, v)| **v == id).map(|(k, _)| k)
|
|
);
|
|
}
|
|
}
|
|
if test_num == 1 || test_num == 2 {
|
|
// Only type 1 results should be added to the list.
|
|
for item in changes.added() {
|
|
let id = Id::from_str(item.id()).unwrap();
|
|
assert!(
|
|
type1_ids.contains(&id),
|
|
"{:?} (id: {:?})",
|
|
changes,
|
|
id_map.iter().find(|(_, v)| **v == id).map(|(k, _)| k)
|
|
);
|
|
}
|
|
}
|
|
if test_num == 3 {
|
|
// Only ids up to 7 should be added to the list.
|
|
for item in changes.added() {
|
|
let item_id = Id::from_str(item.id()).unwrap();
|
|
let id = id_map.iter().find(|(_, v)| **v == item_id).unwrap().0;
|
|
assert!(id < &7, "{:?} (id: {})", changes, id);
|
|
}
|
|
}
|
|
|
|
if let State::Initial = state {
|
|
new_state = State::parse_str(changes.new_query_state()).unwrap();
|
|
}
|
|
}
|
|
}
|
|
states.push(new_state);
|
|
}
|
|
|
|
destroy_all_mailboxes(params).await;
|
|
assert_is_empty(server).await;
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct QueryChanges {
|
|
pub filter: Option<Filter<email::query::Filter>>,
|
|
pub sort: Vec<Comparator<email::query::Comparator>>,
|
|
pub since_query_state: State,
|
|
pub max_changes: usize,
|
|
pub up_to_id: Option<String>,
|
|
}
|