Email/changes tests passing

This commit is contained in:
Mauro D
2023-05-05 08:01:12 +00:00
parent e0a77d1569
commit f928be38ad
35 changed files with 1893 additions and 76 deletions

View File

@@ -20,6 +20,42 @@
"sentAt": "1998-08-13T07:42:41Z",
"bodyStructure": {
"headers": [
{
"name": "Return-Path",
"value": " <president@whitehouse.gov>"
},
{
"name": "Received",
"value": " from mailhost.whitehouse.gov ([192.168.51.200])\n by heartbeat.whitehouse.gov (8.8.8/8.8.8) with ESMTP id SAA22453\n for <vice-president@heartbeat.whitehouse.gov>;\n Mon, 13 Aug 1998 l8:14:23 +1000"
},
{
"name": "Received",
"value": " from the_big_box.whitehouse.gov ([192.168.51.50])\n by mailhost.whitehouse.gov (8.8.8/8.8.7) with ESMTP id RAA20366\n for vice-president@whitehouse.gov; Mon, 13 Aug 1998 17:42:41 +1000"
},
{
"name": "Date",
"value": " Mon, 13 Aug 1998 17:42:41 +1000"
},
{
"name": "Message-ID",
"value": " <199804130742.RAA20366@mai1host.whitehouse.gov>"
},
{
"name": "From",
"value": " Bill Clinton <president@whitehouse.gov>"
},
{
"name": "To",
"value": " A1 (The Enforcer) Gore <vice-president@whitehouse.gov>"
},
{
"name": "Subject",
"value": " Map of Argentina with Description"
},
{
"name": "MIME-Version",
"value": " 1.0"
},
{
"name": "Content-Type",
"value": " multipart/mixed;\n boundary=\"DC8------------DC8638F443D87A7F0726DEF7\""

View File

@@ -20,6 +20,30 @@
"sentAt": "2021-12-14T10:48:25Z",
"bodyStructure": {
"headers": [
{
"name": "To",
"value": " \"email@example.com\" <email@example.com>"
},
{
"name": "From",
"value": " Name <email@example.com>"
},
{
"name": "Subject",
"value": " HTML test"
},
{
"name": "Message-ID",
"value": " <random-message-id@example.com>"
},
{
"name": "Date",
"value": " Tue, 14 Dec 2021 11:48:25 +0100"
},
{
"name": "MIME-Version",
"value": " 1.0"
},
{
"name": "Content-Type",
"value": " multipart/alternative;\r\n boundary=\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""

View File

@@ -0,0 +1,294 @@
use std::sync::Arc;
use jmap::JMAP;
use jmap_client::client::Client;
use jmap_proto::{
parser::{json::Parser, JsonObjectParser},
types::{collection::Collection, id::Id, state::State},
};
use store::{
ahash::AHashSet,
write::{log::ChangeLogBuilder, BatchBuilder},
};
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
println!("Running Email Changes tests...");
let mut states = vec![State::Initial];
for (change_id, (changes, expected_changelog)) in [
(
vec![
LogAction::Insert(0),
LogAction::Insert(1),
LogAction::Insert(2),
],
vec![vec![vec![0, 1, 2], vec![], vec![]]],
),
(
vec![
LogAction::Move(0, 3),
LogAction::Insert(4),
LogAction::Insert(5),
LogAction::Update(1),
LogAction::Update(2),
],
vec![
vec![vec![1, 2, 3, 4, 5], vec![], vec![]],
vec![vec![3, 4, 5], vec![1, 2], vec![0]],
],
),
(
vec![
LogAction::Delete(1),
LogAction::Insert(6),
LogAction::Insert(7),
LogAction::Update(2),
],
vec![
vec![vec![2, 3, 4, 5, 6, 7], vec![], vec![]],
vec![vec![3, 4, 5, 6, 7], vec![2], vec![0, 1]],
vec![vec![6, 7], vec![2], vec![1]],
],
),
(
vec![
LogAction::Update(4),
LogAction::Update(5),
LogAction::Update(6),
LogAction::Update(7),
],
vec![
vec![vec![2, 3, 4, 5, 6, 7], vec![], vec![]],
vec![vec![3, 4, 5, 6, 7], vec![2], vec![0, 1]],
vec![vec![6, 7], vec![2, 4, 5], vec![1]],
vec![vec![], vec![4, 5, 6, 7], vec![]],
],
),
(
vec![
LogAction::Delete(4),
LogAction::Delete(5),
LogAction::Delete(6),
LogAction::Delete(7),
],
vec![
vec![vec![2, 3], vec![], vec![]],
vec![vec![3], vec![2], vec![0, 1]],
vec![vec![], vec![2], vec![1, 4, 5]],
vec![vec![], vec![], vec![4, 5, 6, 7]],
vec![vec![], vec![], vec![4, 5, 6, 7]],
],
),
(
vec![
LogAction::Insert(8),
LogAction::Insert(9),
LogAction::Insert(10),
LogAction::Update(3),
],
vec![
vec![vec![2, 3, 8, 9, 10], vec![], vec![]],
vec![vec![3, 8, 9, 10], vec![2], vec![0, 1]],
vec![vec![8, 9, 10], vec![2, 3], vec![1, 4, 5]],
vec![vec![8, 9, 10], vec![3], vec![4, 5, 6, 7]],
vec![vec![8, 9, 10], vec![3], vec![4, 5, 6, 7]],
vec![vec![8, 9, 10], vec![3], vec![]],
],
),
(
vec![LogAction::Update(2), LogAction::Update(8)],
vec![
vec![vec![2, 3, 8, 9, 10], vec![], vec![]],
vec![vec![3, 8, 9, 10], vec![2], vec![0, 1]],
vec![vec![8, 9, 10], vec![2, 3], vec![1, 4, 5]],
vec![vec![8, 9, 10], vec![2, 3], vec![4, 5, 6, 7]],
vec![vec![8, 9, 10], vec![2, 3], vec![4, 5, 6, 7]],
vec![vec![8, 9, 10], vec![2, 3], vec![]],
vec![vec![], vec![2, 8], vec![]],
],
),
(
vec![
LogAction::Move(9, 11),
LogAction::Move(10, 12),
LogAction::Delete(8),
],
vec![
vec![vec![2, 3, 11, 12], vec![], vec![]],
vec![vec![3, 11, 12], vec![2], vec![0, 1]],
vec![vec![11, 12], vec![2, 3], vec![1, 4, 5]],
vec![vec![11, 12], vec![2, 3], vec![4, 5, 6, 7]],
vec![vec![11, 12], vec![2, 3], vec![4, 5, 6, 7]],
vec![vec![11, 12], vec![2, 3], vec![]],
vec![vec![11, 12], vec![2], vec![8, 9, 10]],
vec![vec![11, 12], vec![], vec![8, 9, 10]],
],
),
]
.into_iter()
.enumerate()
{
let mut changelog = ChangeLogBuilder::with_change_id(change_id as u64);
for change in changes {
match change {
LogAction::Insert(id) => changelog.log_insert(Collection::Email, id),
LogAction::Update(id) => changelog.log_update(Collection::Email, id),
LogAction::Delete(id) => changelog.log_delete(Collection::Email, id),
LogAction::UpdateChild(id) => changelog.log_child_update(Collection::Email, id),
LogAction::Move(old_id, new_id) => {
changelog.log_move(Collection::Email, old_id, new_id)
}
}
}
server
.store
.write(
BatchBuilder::new()
.with_account_id(1)
.with_collection(Collection::Email)
.custom(changelog)
.build_batch(),
)
.await
.unwrap();
let mut new_state = State::Initial;
for (test_num, state) in (states).iter().enumerate() {
let changes = client.email_changes(state.to_string(), None).await.unwrap();
assert_eq!(
expected_changelog[test_num],
[changes.created(), changes.updated(), changes.destroyed()]
.into_iter()
.map(|list| {
let mut list = list
.iter()
.map(|i| Id::from_bytes(i.as_bytes()).unwrap().into())
.collect::<Vec<u64>>();
list.sort_unstable();
list
})
.collect::<Vec<Vec<_>>>(),
"test_num: {}, state: {:?}",
test_num,
state
);
if let State::Initial = state {
new_state = State::parse_str(changes.new_state()).unwrap();
}
for max_changes in 1..=8 {
let mut insertions = expected_changelog[test_num][0]
.iter()
.copied()
.collect::<AHashSet<_>>();
let mut updates = expected_changelog[test_num][1]
.iter()
.copied()
.collect::<AHashSet<_>>();
let mut deletions = expected_changelog[test_num][2]
.iter()
.copied()
.collect::<AHashSet<_>>();
let mut int_state = state.clone();
for _ in 0..100 {
let changes = client
.email_changes(int_state.to_string(), max_changes.into())
.await
.unwrap();
assert!(
changes.created().len()
+ changes.updated().len()
+ changes.destroyed().len()
<= max_changes,
"{} > {}",
changes.created().len()
+ changes.updated().len()
+ changes.destroyed().len(),
max_changes
);
changes.created().iter().for_each(|id| {
assert!(
insertions.remove(&Id::from_bytes(id.as_bytes()).unwrap()),
"{:?} != {}",
insertions,
Id::from_bytes(id.as_bytes()).unwrap()
);
});
changes.updated().iter().for_each(|id| {
assert!(
updates.remove(&Id::from_bytes(id.as_bytes()).unwrap()),
"{:?} != {}",
updates,
Id::from_bytes(id.as_bytes()).unwrap()
);
});
changes.destroyed().iter().for_each(|id| {
assert!(
deletions.remove(&Id::from_bytes(id.as_bytes()).unwrap()),
"{:?} != {}",
deletions,
Id::from_bytes(id.as_bytes()).unwrap()
);
});
int_state = State::parse_str(changes.new_state()).unwrap();
if !changes.has_more_changes() {
break;
}
}
assert_eq!(insertions.len(), 0);
assert_eq!(updates.len(), 0);
assert_eq!(deletions.len(), 0);
}
}
states.push(new_state);
}
let changes = client
.email_changes(State::Initial.to_string(), 0.into())
.await
.unwrap();
let mut created = changes
.created()
.iter()
.map(|i| Id::from_bytes(i.as_bytes()).unwrap().into())
.collect::<Vec<u64>>();
created.sort_unstable();
assert_eq!(created, vec![2, 3, 11, 12]);
assert_eq!(changes.updated(), Vec::<String>::new());
assert_eq!(changes.destroyed(), Vec::<String>::new());
}
#[derive(Debug, Clone, Copy)]
pub enum LogAction {
Insert(u64),
Update(u64),
Delete(u64),
UpdateChild(u64),
Move(u64, u64),
}
pub trait ParseState: Sized {
fn parse_str(state: &str) -> Option<Self>;
}
impl ParseState for State {
fn parse_str(state: &str) -> Option<Self> {
let state = format!("{state}\"");
let mut parser = Parser::new(state.as_bytes());
State::parse(&mut parser).ok()
}
}

View File

@@ -0,0 +1,224 @@
use std::{fs, path::PathBuf, sync::Arc};
use jmap::JMAP;
use jmap_client::{
client::Client,
email::{self, Header, HeaderForm},
mailbox::Role,
};
use jmap_proto::types::id::Id;
use crate::jmap::{email_get::all_headers, replace_blob_ids};
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
println!("Running Email Parse tests...");
let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
test_dir.push("resources");
test_dir.push("jmap_mail_parse");
let mailbox_id = client
.set_default_account_id(Id::new(1).to_string())
.mailbox_create("JMAP Parse", None::<String>, Role::None)
.await
.unwrap()
.take_id();
// Test parsing an email attachment
for test_name in ["attachment.eml", "attachment_b64.eml"] {
let mut test_file = test_dir.clone();
test_file.push(test_name);
let email = client
.email_import(
fs::read(&test_file).unwrap(),
[mailbox_id.clone()],
None::<Vec<String>>,
None,
)
.await
.unwrap();
let blob_id = client
.email_get(email.id().unwrap(), Some([email::Property::Attachments]))
.await
.unwrap()
.unwrap()
.attachments()
.unwrap()
.first()
.unwrap()
.blob_id()
.unwrap()
.to_string();
let email = client
.email_parse(
&blob_id,
[
email::Property::Id,
email::Property::BlobId,
email::Property::ThreadId,
email::Property::MailboxIds,
email::Property::Keywords,
email::Property::Size,
email::Property::ReceivedAt,
email::Property::MessageId,
email::Property::InReplyTo,
email::Property::References,
email::Property::Sender,
email::Property::From,
email::Property::To,
email::Property::Cc,
email::Property::Bcc,
email::Property::ReplyTo,
email::Property::Subject,
email::Property::SentAt,
email::Property::HasAttachment,
email::Property::Preview,
email::Property::BodyValues,
email::Property::TextBody,
email::Property::HtmlBody,
email::Property::Attachments,
email::Property::BodyStructure,
]
.into(),
[
email::BodyProperty::PartId,
email::BodyProperty::BlobId,
email::BodyProperty::Size,
email::BodyProperty::Name,
email::BodyProperty::Type,
email::BodyProperty::Charset,
email::BodyProperty::Headers,
email::BodyProperty::Disposition,
email::BodyProperty::Cid,
email::BodyProperty::Language,
email::BodyProperty::Location,
]
.into(),
100.into(),
)
.await
.unwrap();
if !test_name.contains("_b64") {
for parts in [
email.text_body().unwrap(),
email.html_body().unwrap(),
email.attachments().unwrap(),
] {
for part in parts {
let blob_id = part.blob_id().unwrap();
let inner_blob = client.download(blob_id).await.unwrap();
test_file.set_extension(format!("part{}", part.part_id().unwrap()));
//fs::write(&test_file, inner_blob).unwrap();
let expected_inner_blob = fs::read(&test_file).unwrap();
assert_eq!(
inner_blob,
expected_inner_blob,
"file: {}",
test_file.display()
);
}
}
}
test_file.set_extension("json");
let result = replace_blob_ids(serde_json::to_string_pretty(&email.into_test()).unwrap());
if fs::read(&test_file).unwrap() != result.as_bytes() {
test_file.set_extension("failed");
fs::write(&test_file, result.as_bytes()).unwrap();
panic!("Test failed, output saved to {}", test_file.display());
}
}
// Test header parsing on a temporary blob
let mut test_file = test_dir;
test_file.push("headers.eml");
let blob_id = client
.upload(None, fs::read(&test_file).unwrap(), None)
.await
.unwrap()
.take_blob_id();
let mut email = client
.email_parse(
&blob_id,
[
email::Property::Id,
email::Property::MessageId,
email::Property::InReplyTo,
email::Property::References,
email::Property::Sender,
email::Property::From,
email::Property::To,
email::Property::Cc,
email::Property::Bcc,
email::Property::ReplyTo,
email::Property::Subject,
email::Property::SentAt,
email::Property::Preview,
email::Property::TextBody,
email::Property::HtmlBody,
email::Property::Attachments,
]
.into(),
[
email::BodyProperty::Size,
email::BodyProperty::Name,
email::BodyProperty::Type,
email::BodyProperty::Charset,
email::BodyProperty::Disposition,
email::BodyProperty::Cid,
email::BodyProperty::Language,
email::BodyProperty::Location,
email::BodyProperty::Header(Header {
name: "X-Custom-Header".into(),
form: HeaderForm::Raw,
all: false,
}),
email::BodyProperty::Header(Header {
name: "X-Custom-Header-2".into(),
form: HeaderForm::Raw,
all: false,
}),
]
.into(),
100.into(),
)
.await
.unwrap()
.into_test();
for property in all_headers() {
email.headers.extend(
client
.email_parse(&blob_id, [property].into(), [].into(), None)
.await
.unwrap()
.into_test()
.headers,
);
}
test_file.set_extension("json");
let result = replace_blob_ids(serde_json::to_string_pretty(&email).unwrap());
if fs::read(&test_file).unwrap() != result.as_bytes() {
test_file.set_extension("failed");
fs::write(&test_file, result.as_bytes()).unwrap();
panic!("Test failed, output saved to {}", test_file.display());
}
client.mailbox_destroy(&mailbox_id, true).await.unwrap();
server.store.assert_is_empty().await;
}

View File

@@ -0,0 +1,276 @@
use std::sync::Arc;
use jmap::JMAP;
use jmap_client::{
client::Client,
core::query::{Comparator, Filter},
email,
mailbox::Role,
};
use jmap_proto::types::{collection::Collection, id::Id, property::Property, state::State};
use store::{
ahash::{AHashMap, AHashSet},
write::{log::ChangeLogBuilder, BatchBuilder, F_BITMAP, F_CLEAR, F_VALUE},
};
use crate::jmap::email_changes::{LogAction, ParseState};
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
println!("Running Email QueryChanges tests...");
let mailbox1_id = 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 = 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_bytes(
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()
.as_bytes(),
)
.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 changelog = ChangeLogBuilder::new();
changelog.log_update(Collection::Email, id);
server.commit_changes(1, changelog).await.unwrap();
updated_ids.insert(id);
}
LogAction::Delete(id) => {
let id = *id_map.get(id).unwrap();
client.email_destroy(&id.to_string()).await.unwrap();
// Delete virtual threadId created during tests (so assert_empty_store succeeds)
server
.store
.write(
BatchBuilder::new()
.with_account_id(1)
.with_collection(Collection::Email)
.update_document(id.document_id())
.bitmap(Property::ThreadId, id.prefix_id(), F_CLEAR)
.build_batch(),
)
.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());
server
.store
.write(
BatchBuilder::new()
.with_account_id(1)
.with_collection(Collection::Email)
.update_document(id.document_id())
.value(Property::ThreadId, id.prefix_id(), F_BITMAP | F_CLEAR)
.value(Property::ThreadId, thread_id, F_VALUE | F_BITMAP)
.custom(server.begin_changes(1).await.unwrap().with_log_move(
Collection::Email,
id,
new_id,
))
.build_batch(),
)
.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 = 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_bytes(id.as_bytes()).unwrap();
assert!(
removed_ids.contains(&id),
"{:?} (id: {})",
changes,
id_map.iter().find(|(_, v)| **v == id).unwrap().0
);
}
}
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_bytes(item.id().as_bytes()).unwrap();
assert!(
type1_ids.contains(&id),
"{:?} (id: {})",
changes,
id_map.iter().find(|(_, v)| **v == id).unwrap().0
);
}
}
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_bytes(item.id().as_bytes()).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);
}
client.mailbox_destroy(&mailbox1_id, true).await.unwrap();
client.mailbox_destroy(&mailbox2_id, true).await.unwrap();
server.store.assert_is_empty().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>,
}

View File

@@ -0,0 +1,163 @@
use std::{fs, path::PathBuf, sync::Arc};
use jmap::JMAP;
use jmap_client::{client::Client, core::query, email::query::Filter, mailbox::Role};
use jmap_proto::types::id::Id;
use store::ahash::AHashMap;
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
println!("Running SearchSnippet tests...");
let mailbox_id = client
.set_default_account_id(Id::new(1).to_string())
.mailbox_create("JMAP SearchSnippet", None::<String>, Role::None)
.await
.unwrap()
.take_id();
let mut email_ids = AHashMap::default();
let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
test_dir.push("resources");
test_dir.push("jmap_mail_snippet");
// Import test messages
for email_name in [
"html",
"subpart",
"mixed",
"text_plain",
"text_plain_chinese",
] {
let mut file_name = test_dir.clone();
file_name.push(format!("{}.eml", email_name));
let email_id = client
.email_import(
fs::read(&file_name).unwrap(),
[&mailbox_id],
None::<Vec<&str>>,
None,
)
.await
.unwrap()
.take_id();
email_ids.insert(email_name, email_id);
}
// Run tests
for (filter, email_name, snippet_subject, snippet_preview) in [
(
query::Filter::or(vec![
query::Filter::or(vec![Filter::subject("friend"), Filter::subject("help")]),
query::Filter::or(vec![Filter::body("secret"), Filter::body("call")]),
]),
"text_plain",
Some("<mark>Help</mark> a <mark>friend</mark> from Abidjan Côte d'Ivoire"),
Some(concat!(
"d'Ivoire. He <mark>secretly</mark> <mark>called</mark> me on his bedside ",
"and told me that he has a sum of $7.5M (Seven Million five Hundred Thousand",
" Dollars) left in a suspense account in a local bank here in Abidjan Côte ",
"d'Ivoire, that he used my name a"
)),
),
(
Filter::text("côte").into(),
"text_plain",
Some("Help a friend from Abidjan <mark>Côte</mark> d'Ivoire"),
Some(concat!(
"in Abidjan <mark>Côte</mark> d'Ivoire. He secretly called me on ",
"his bedside and told me that he has a sum of $7.5M (Seven ",
"Million five Hundred Thousand Dollars) left in a suspense ",
"account in a local bank here in Abidjan <mark>Côte</mark> d'Ivoire, that "
)),
),
(
Filter::text("\"your country\"").into(),
"text_plain",
None,
Some(concat!(
"over to <mark>your</mark> <mark>country</mark> to further my education and ",
"to secure a residential permit for me in <mark>your</mark> <mark>country",
"</mark>. Moreover, I am willing to offer you 30 percent of the total sum as ",
"compensation for your effort inp",
)),
),
(
Filter::text("overseas").into(),
"text_plain",
None,
Some("nominated account <mark>overseas</mark>. "),
),
(
Filter::text("孫子兵法").into(),
"text_plain_chinese",
Some("<mark>孫</mark><mark>子</mark><mark>兵法</mark>"),
Some(concat!(
"&lt;&quot;<mark>孫</mark><mark>子</mark><mark>兵法</mark>&quot;&gt; ",
"<mark>孫</mark><mark>子</mark>曰:兵者,國之大事,死生之地,存亡之道,",
"不可不察也。 <mark>孫</mark><mark>子</mark>曰:凡用兵之法,馳車千駟"
)),
),
(
Filter::text("cia").into(),
"subpart",
None,
Some("shouldn't the <mark>CIA</mark> have something like that? Bill"),
),
(
Filter::text("frösche").into(),
"html",
Some("Die Hasen und die <mark>Frösche</mark>"),
Some(concat!(
"und die <mark>Frösche</mark> Die Hasen klagten einst über ihre mißliche Lage; ",
"&quot;wir leben&quot;, sprach ein Redner, &quot;in steter Furcht vor Menschen und ",
"Tieren, eine Beute der Hunde, der Adler, ja fast aller Raubtiere! ",
"Unsere stete Angst ist är")),
),
(
Filter::text("es:galería vasto biblioteca").into(),
"mixed",
Some("<mark>Biblioteca</mark> de Babel"),
Some(concat!(
"llaman la *<mark>Biblioteca</mark>*) se compone de un número indefinido, y tal ",
"vez infinito, de <mark>galerías</mark> hexagonales, con <mark>vastos</mark> ",
"pozos de ventilación en el medio, cercados por barandas bajísimas. Desde ",
"cualquier hexágono se "
)),
),
] {
let mut request = client.build();
let result_ref = request
.query_email()
.filter(filter.clone())
.result_reference();
request
.get_search_snippet()
.filter(filter)
.email_ids_ref(result_ref);
let response = request
.send()
.await
.unwrap()
.unwrap_method_responses()
.pop()
.unwrap()
.unwrap_get_search_snippet()
.unwrap();
let snippet = response
.snippet(email_ids.get(email_name).unwrap())
.unwrap_or_else(|| panic!("No snippet for {}", email_name));
assert_eq!(snippet_subject, snippet.subject());
assert_eq!(snippet_preview, snippet.preview());
assert!(
snippet.preview().map_or(0, |p| p.len()) <= 255,
"len: {}",
snippet.preview().map_or(0, |p| p.len())
);
}
// Destroy test data
client.mailbox_destroy(&mailbox_id, true).await.unwrap();
server.store.assert_is_empty().await;
}

View File

@@ -7,8 +7,12 @@ use tokio::sync::watch;
use crate::{add_test_certs, store::TempDir};
pub mod email_changes;
pub mod email_get;
pub mod email_parse;
pub mod email_query;
pub mod email_query_changes;
pub mod email_search_snippet;
pub mod email_set;
pub mod mailbox;
pub mod thread_get;
@@ -38,6 +42,10 @@ blob.path = '{TMP}'
[certificate.default]
cert = 'file://{CERT}'
private-key = 'file://{PK}'
[jmap.protocol]
set.max-objects = 100000
";
#[tokio::test]
@@ -51,12 +59,16 @@ pub async fn jmap_tests() {
let delete = true;
let mut params = init_jmap_tests(delete).await;
//email_query::test(params.server.clone(), &mut params.client, delete).await;
//email_get::test(params.server.clone(), &mut params.client).await;
//email_set::test(params.server.clone(), &mut params.client).await;
//email_query::test(params.server.clone(), &mut params.client, delete).await;
//email_parse::test(params.server.clone(), &mut params.client).await;
//email_search_snippet::test(params.server.clone(), &mut params.client).await;
//email_changes::test(params.server.clone(), &mut params.client).await;
email_query_changes::test(params.server.clone(), &mut params.client).await;
//thread_get::test(params.server.clone(), &mut params.client).await;
//thread_merge::test(params.server.clone(), &mut params.client).await;
mailbox::test(params.server.clone(), &mut params.client).await;
//mailbox::test(params.server.clone(), &mut params.client).await;
if delete {
params.temp_dir.delete();
}