First API tests.

This commit is contained in:
Mauro D
2023-04-19 15:09:37 +00:00
parent c807883374
commit 0264890595
70 changed files with 9506 additions and 15 deletions

353
tests/src/jmap/email_get.rs Normal file
View File

@@ -0,0 +1,353 @@
/*
* Copyright (c) 2020-2022, Stalwart Labs Ltd.
*
* This file is part of the Stalwart JMAP Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
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 mail_parser::{HeaderName, RfcHeader};
use crate::jmap::replace_blob_ids;
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
println!("Running Email Get tests...");
let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
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();*/
for file_name in fs::read_dir(&test_dir).unwrap() {
let mut file_name = file_name.as_ref().unwrap().path();
if file_name.extension().map_or(true, |e| e != "eml") {
continue;
}
let is_headers_test = file_name.file_name().unwrap() == "headers.eml";
let blob = fs::read(&file_name).unwrap();
let blob_len = blob.len();
let email = client
.email_import(
blob,
[mailbox_id.clone()],
["tag".to_string()].into(),
((blob_len * 1000000) as i64).into(),
)
.await
.unwrap();
let mut request = client.build();
request
.get_email()
.ids([email.id().unwrap()])
.properties([
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,
])
.arguments()
.body_properties(if !is_headers_test {
[
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,
]
} else {
[
email::BodyProperty::PartId,
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,
}),
]
})
.fetch_all_body_values(true)
.max_body_value_bytes(100);
let mut result = request
.send_get_email()
.await
.unwrap()
.take_list()
.pop()
.unwrap()
.into_test();
if is_headers_test {
for property in all_headers() {
let mut request = client.build();
request
.get_email()
.ids([email.id().unwrap()])
.properties([property]);
result.headers.extend(
request
.send_get_email()
.await
.unwrap()
.take_list()
.pop()
.unwrap()
.into_test()
.headers,
);
}
}
let result = replace_blob_ids(serde_json::to_string_pretty(&result).unwrap());
file_name.set_extension("json");
if fs::read(&file_name).unwrap() != result.as_bytes() {
file_name.set_extension("failed");
fs::write(&file_name, result.as_bytes()).unwrap();
panic!("Test failed, output saved to {}", file_name.display());
}
}
let coco = "implement";
//client.mailbox_destroy(&mailbox_id, true).await.unwrap();
//server.store.assert_is_empty();
}
pub fn all_headers() -> Vec<email::Property> {
let mut properties = Vec::new();
for header in [
HeaderName::Rfc(RfcHeader::From),
HeaderName::Rfc(RfcHeader::To),
HeaderName::Rfc(RfcHeader::Cc),
HeaderName::Rfc(RfcHeader::Bcc),
HeaderName::Other("X-Address-Single".into()),
HeaderName::Other("X-Address".into()),
HeaderName::Other("X-AddressList-Single".into()),
HeaderName::Other("X-AddressList".into()),
HeaderName::Other("X-AddressesGroup-Single".into()),
HeaderName::Other("X-AddressesGroup".into()),
] {
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: false,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Addresses,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Addresses,
name: header.as_str().to_string(),
all: false,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::GroupedAddresses,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::GroupedAddresses,
name: header.as_str().to_string(),
all: false,
}));
}
for header in [
HeaderName::Rfc(RfcHeader::ListPost),
HeaderName::Rfc(RfcHeader::ListSubscribe),
HeaderName::Rfc(RfcHeader::ListUnsubscribe),
HeaderName::Rfc(RfcHeader::ListOwner),
HeaderName::Other("X-List-Single".into()),
HeaderName::Other("X-List".into()),
] {
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: false,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::URLs,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::URLs,
name: header.as_str().to_string(),
all: false,
}));
}
for header in [
HeaderName::Rfc(RfcHeader::Date),
HeaderName::Rfc(RfcHeader::ResentDate),
HeaderName::Other("X-Date-Single".into()),
HeaderName::Other("X-Date".into()),
] {
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: false,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Date,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Date,
name: header.as_str().to_string(),
all: false,
}));
}
for header in [
HeaderName::Rfc(RfcHeader::MessageId),
HeaderName::Rfc(RfcHeader::References),
HeaderName::Other("X-Id-Single".into()),
HeaderName::Other("X-Id".into()),
] {
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: false,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::MessageIds,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::MessageIds,
name: header.as_str().to_string(),
all: false,
}));
}
for header in [
HeaderName::Rfc(RfcHeader::Subject),
HeaderName::Rfc(RfcHeader::Keywords),
HeaderName::Other("X-Text-Single".into()),
HeaderName::Other("X-Text".into()),
] {
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Raw,
name: header.as_str().to_string(),
all: false,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Text,
name: header.as_str().to_string(),
all: true,
}));
properties.push(email::Property::Header(Header {
form: HeaderForm::Text,
name: header.as_str().to_string(),
all: false,
}));
}
properties
}

149
tests/src/jmap/mod.rs Normal file
View File

@@ -0,0 +1,149 @@
use std::sync::Arc;
use jmap::{api::SessionManager, JMAP};
use jmap_client::client::{Client, Credentials};
use jmap_proto::types::id::Id;
use tokio::sync::watch;
use crate::{add_test_certs, store::TempDir};
pub mod email_get;
const SERVER: &str = "
[server]
hostname = 'jmap.example.org'
[server.listener.jmap]
bind = ['127.0.0.1:8899']
url = 'https://127.0.0.1:8899'
protocol = 'jmap'
[server.socket]
reuse-addr = true
[server.tls]
enable = true
implicit = false
certificate = 'default'
[store]
db.path = '{TMP}/sqlite.db'
blob.path = '{TMP}'
[certificate.default]
cert = 'file://{CERT}'
private-key = 'file://{PK}'
";
#[tokio::test]
pub async fn jmap_tests() {
tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::WARN)
.finish(),
)
.unwrap();
let delete = true;
let mut params = init_jmap_tests(delete).await;
email_get::test(params.server.clone(), &mut params.client).await;
if delete {
params.temp_dir.delete();
}
}
#[allow(dead_code)]
struct JMAPTest {
server: Arc<JMAP>,
client: Client,
temp_dir: TempDir,
shutdown_tx: watch::Sender<bool>,
}
async fn init_jmap_tests(delete_if_exists: bool) -> JMAPTest {
// Load and parse config
let temp_dir = TempDir::new("jmap_tests", delete_if_exists);
let settings = utils::config::Config::parse(
&add_test_certs(SERVER).replace("{TMP}", &temp_dir.path.display().to_string()),
)
.unwrap();
let servers = settings.parse_servers().unwrap();
// Start JMAP server
let manager = SessionManager::from(JMAP::new(&settings).await);
let shutdown_tx = servers.spawn(&settings, |server, shutdown_rx| {
server.spawn(manager.clone(), shutdown_rx);
});
// Create client
let mut client = Client::new()
.credentials(Credentials::bearer("DO_NOT_ATTEMPT_THIS_AT_HOME"))
.accept_invalid_certs(true)
.connect("https://127.0.0.1:8899")
.await
.unwrap();
client.set_default_account_id(Id::new(1));
JMAPTest {
server: manager.inner,
temp_dir,
client,
shutdown_tx,
}
}
pub fn find_values(string: &str, name: &str) -> Vec<String> {
let mut last_pos = 0;
let mut values = Vec::new();
while let Some(pos) = string[last_pos..].find(name) {
let mut value = string[last_pos + pos + name.len()..]
.split('"')
.nth(1)
.unwrap();
if value.ends_with('\\') {
value = &value[..value.len() - 1];
}
values.push(value.to_string());
last_pos += pos + name.len();
}
values
}
pub fn replace_values(mut string: String, find: &[String], replace: &[String]) -> String {
for (find, replace) in find.iter().zip(replace.iter()) {
string = string.replace(find, replace);
}
string
}
pub fn replace_boundaries(string: String) -> String {
let values = find_values(&string, "boundary=");
if !values.is_empty() {
replace_values(
string,
&values,
&(0..values.len())
.map(|i| format!("boundary_{}", i))
.collect::<Vec<_>>(),
)
} else {
string
}
}
pub fn replace_blob_ids(string: String) -> String {
let values = find_values(&string, "blobId\":");
if !values.is_empty() {
replace_values(
string,
&values,
&(0..values.len())
.map(|i| format!("blob_{}", i))
.collect::<Vec<_>>(),
)
} else {
string
}
}

View File

@@ -1,2 +1,20 @@
use std::path::PathBuf;
#[cfg(test)]
pub mod jmap;
#[cfg(test)]
pub mod store;
pub fn add_test_certs(config: &str) -> String {
let mut cert_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
cert_path.push("resources");
let mut cert = cert_path.clone();
cert.push("tls_cert.pem");
let mut pk = cert_path.clone();
pk.push("tls_privatekey.pem");
config
.replace("{CERT}", cert.as_path().to_str().unwrap())
.replace("{PK}", pk.as_path().to_str().unwrap())
}

View File

@@ -7,15 +7,20 @@ use std::{io::Read, sync::Arc};
use ::store::Store;
use utils::config::Config;
struct TempDir {
path: std::path::PathBuf,
pub struct TempDir {
pub path: std::path::PathBuf,
}
#[tokio::test]
pub async fn store_test() {
let temp_dir = TempDir::new("store_tests", true);
pub async fn store_tests() {
let insert = false;
let temp_dir = TempDir::new("store_tests", insert);
let config_file = format!(
concat!("[blob.store]\n", "path = \"{}\"\n", "hash = 1\n"),
concat!(
"store.blob.path = \"{}\"\n",
"store.db.path = \"{}/sqlite.db\"\n"
),
temp_dir.path.display(),
temp_dir.path.display()
);
let db = Arc::new(
@@ -23,20 +28,17 @@ pub async fn store_test() {
.await
.unwrap(),
);
let insert = true;
if insert {
db.destroy().await;
}
//assign_id::test(db).await;
blobs::test(db).await;
//query::test(db, insert).await;
//blobs::test(db).await;
query::test(db, insert).await;
temp_dir.delete();
}
pub fn deflate_artwork_data() -> Vec<u8> {
let mut csv_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
csv_path.push("src");
csv_path.push("tests");
csv_path.push("resources");
csv_path.push("artwork_data.csv.gz");

View File

@@ -26,6 +26,7 @@ use std::{
time::Instant,
};
use jmap_proto::types::keyword::Keyword;
use store::{ahash::AHashMap, query::sort::Pagination};
use store::{
@@ -154,7 +155,7 @@ pub async fn test(db: Arc<Store>, do_insert: bool) {
if !field.is_empty() {
builder.value(
field_id,
field.to_lowercase(),
Keyword::Other(field.to_lowercase()),
F_VALUE | F_INDEX | F_BITMAP,
);
}
@@ -249,7 +250,7 @@ pub async fn test_filter(db: Arc<Store>) {
(
vec![
Filter::has_text(fields["artist"], "mauro kunst", Language::None),
Filter::has_keyword(fields["artistRole"], "artist"),
Filter::is_in_bitmap(fields["artistRole"], Keyword::Other("artist".to_string())),
Filter::Or,
Filter::eq(fields["year"], 1969u32),
Filter::eq(fields["year"], 1971u32),
@@ -283,7 +284,7 @@ pub async fn test_filter(db: Arc<Store>) {
(
vec![
Filter::And,
Filter::has_keyword(fields["artist"], "warhol"),
Filter::has_text(fields["artist"], "warhol", Language::None),
Filter::Not,
Filter::has_english_text(fields["title"], "'campbell'"),
Filter::End,