diff options
| author | Dirk Van Haerenborgh <vhdirk@gmail.com> | 2019-11-13 14:56:32 +0100 |
|---|---|---|
| committer | Dirk Van Haerenborgh <vhdirk@gmail.com> | 2019-11-13 14:56:32 +0100 |
| commit | 3f795961db8228da27bb4823fac2f68e57e7276a (patch) | |
| tree | dbf4916ebbd04ea281f4f86d03148c18a4d02339 /tests | |
| parent | 422377021f9a15b30d297b6ead7864098d5b2c2d (diff) | |
| download | mail-3f795961db8228da27bb4823fac2f68e57e7276a.tar.gz | |
port tests from notmuch-python-cffi
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/fixtures.rs | 216 | ||||
| -rw-r--r-- | tests/lib.rs | 11 | ||||
| -rw-r--r-- | tests/main.rs | 91 | ||||
| -rw-r--r-- | tests/test_database.rs | 337 |
4 files changed, 619 insertions, 36 deletions
diff --git a/tests/fixtures.rs b/tests/fixtures.rs new file mode 100644 index 0000000..8887da8 --- /dev/null +++ b/tests/fixtures.rs @@ -0,0 +1,216 @@ +extern crate dirs; +extern crate tempfile; +extern crate notmuch; +extern crate gethostname; +extern crate maildir; +extern crate lettre; +extern crate lettre_email; + +use std::ffi::OsStr; +use std::io::{self, Result, Write}; +use std::fs::{self, File}; +use std::rc::Rc; +use std::path::{Path, PathBuf}; +use tempfile::{tempdir, tempdir_in, Builder, TempDir}; +use std::net::ToSocketAddrs; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; +use maildir::Maildir; +use lettre_email::{EmailBuilder, Header}; +use lettre::SendableEmail; + + +pub fn timestamp_ms() -> u128 { + let start = SystemTime::now(); + let time_since_epoch = start.duration_since(UNIX_EPOCH).unwrap(); + time_since_epoch.as_millis() +} + +// A basic test interface to a valid maildir directory. +// +// This creates a valid maildir and provides a simple mechanism to +// deliver test emails to it. It also writes a notmuch-config file +// in the top of the maildir. +pub struct MailBox { + root_dir: TempDir, + idcount: u32, + maildir: Maildir +} + +impl MailBox { + + // Creates a new maildir fixture. Since this is only used for tests, + // may just panic of something is wrong + pub fn new() -> Self { + + let root_dir = tempdir().unwrap(); + let root_path = root_dir.path().to_path_buf(); + + let tmp_path = root_path.join("tmp"); + fs::create_dir(&tmp_path).unwrap(); + + let cfg_fname = root_path.join("notmuch-config"); + let mut cfg_file = File::create(cfg_fname).unwrap(); + write!(cfg_file, r#" + [database] + path={tmppath} + [user] + name=Some Hacker + primary_email=dst@example.com + [new] + tags=unread;inbox; + ignore= + [search] + exclude_tags=deleted;spam; + [maildir] + synchronize_flags=true + [crypto] + gpg_path=gpg + "#, tmppath=root_path.to_string_lossy()).unwrap(); + + let maildir = Maildir::from(root_path.to_path_buf()); + maildir.create_dirs().unwrap(); + + Self { + root_dir, + idcount: 0, + maildir + } + } + + /// Return a new unique message ID + // fn next_msgid(&mut self) -> String{ + // let hostname = gethostname::gethostname(); + // let msgid = format!("{}@{}", self.idcount, hostname.to_string_lossy()); + // self.idcount += 1; + // msgid + // } + + pub fn path(&self) -> PathBuf + { + self.root_dir.path().into() + } + + pub fn hostname(&self) -> String { + let hname = gethostname::gethostname(); + hname.to_string_lossy().into() + } + + /// Deliver a new mail message in the mbox. + /// This does only adds the message to maildir, does not insert it + /// into the notmuch database. + /// returns a tuple of (msgid, pathname). + pub fn deliver(&self, + subject: Option<String>, + body: Option<String>, + to: Option<String>, + from: Option<String>, + headers: Vec<(String, String)>, + is_new: bool, // Move to new dir or cur dir? + keywords: Option<Vec<String>>, // List of keywords or labels + seen: bool, // Seen flag (cur dir only) + replied: bool, // Replied flag (cur dir only) + flagged: bool) // Flagged flag (cur dir only) + -> Result<(String, PathBuf)> + { + + let mut builder = EmailBuilder::new(); + + if let Some(val) = subject { + builder = builder.subject(val); + } + if let Some(val) = body { + builder = builder.text(val); + } + builder = match to { + Some(val) => builder.to(val), + None => builder.to(format!("to@{}.localhost", self.hostname())) + }; + builder = match from { + Some(val) => builder.from(val), + None => builder.from(format!("from@{}.localhost", self.hostname())) + }; + + for h in headers.into_iter(){ + let hdr: Header = h.into(); + builder = builder.header(hdr); + } + + let msg:SendableEmail = builder.build().unwrap().into(); + + // not sure why lettre doesn't add the host suffix itself + let msg_id = msg.message_id().to_string() + ".lettre@localhost"; + let id = if is_new { + self.maildir.store_new(&msg.message_to_string().unwrap().as_bytes()).unwrap() + }else{ + let mut flags = String::from(""); + if flagged { + flags += "F"; + } + if replied { + flags += "R"; + } + if seen { + flags += "S"; + } + self.maildir.store_cur_with_flags(&msg.message_to_string().unwrap().as_bytes(), flags.as_str()).unwrap() + }; + + let mut msgpath = self.path(); + msgpath = if is_new { + msgpath.join("new") + } else { + msgpath.join("cur") + }; + + msgpath = msgpath.join(&id); + + Ok((msg_id, msgpath)) + } +} + +impl Drop for MailBox { + fn drop(&mut self) { + } +} + + +#[derive(Clone, Debug)] +pub struct NotmuchCommand { + maildir_path: PathBuf +} + +impl NotmuchCommand { + + /// Return a function which runs notmuch commands on our test maildir. + /// + /// This uses the notmuch-config file created by the ``maildir`` + /// fixture. + pub fn new(maildir_path: &PathBuf) -> Self { + Self { + maildir_path: maildir_path.clone() + } + } + + /// Run a notmuch comand. + /// + /// This function runs with a timeout error as many notmuch + /// commands may block if multiple processes are trying to open + /// the database in write-mode. It is all too easy to + /// accidentally do this in the unittests. + pub fn run<I, S>(&self, args: I) -> Result<()> + where + I: IntoIterator<Item=S>, + S: AsRef<OsStr> + { + let cfg_fname = self.maildir_path.join("notmuch-config"); + + Command::new("notmuch").env("NOTMUCH_CONFIG", &cfg_fname) + .args(args) + .status()?; + Ok(()) + } + +} + + diff --git a/tests/lib.rs b/tests/lib.rs new file mode 100644 index 0000000..c5095f4 --- /dev/null +++ b/tests/lib.rs @@ -0,0 +1,11 @@ +extern crate dirs; +extern crate tempfile; +extern crate notmuch; +extern crate gethostname; +extern crate maildir; +extern crate lettre; +extern crate lettre_email; + +mod fixtures; +mod test_database; + diff --git a/tests/main.rs b/tests/main.rs index 9ad0a36..17db2bc 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,43 +1,62 @@ extern crate dirs; +extern crate tempfile; extern crate notmuch; +extern crate gethostname; +extern crate maildir; +extern crate lettre; +extern crate lettre_email; use std::sync::Arc; use notmuch::{Query, QueryExt}; -fn main() { - let mut mail_path = dirs::home_dir().unwrap(); - mail_path.push(".mail"); - - match notmuch::Database::open( - &mail_path.to_str().unwrap().to_string(), - notmuch::DatabaseMode::ReadOnly, - ) { - Ok(db) => { - #[cfg(feature = "v0_21")] - { - let rev = db.revision(); - println!("db revision: {:?}", rev); - } - let query = { - let dbr = Arc::new(db); - - notmuch::Query::create(dbr.clone(), &"".to_string()).unwrap() - }; - - // let mut threads = query.search_threads().unwrap(); - - // let mut threads = db.create_query(&"".to_string()).unwrap().search_threads().unwrap(); - - let mut threads = Arc::new(<Query as QueryExt>::search_threads(query).unwrap()); - - for thread in Arc::get_mut(&mut threads).unwrap() - { - println!("thread {:?} {:?}", thread.subject(), thread.authors()); - } - } - Err(err) => { - println!("Got error while trying to open db: {:?}", err); - } - } -} +mod fixtures; +use fixtures::{MailBox, NotmuchCommand}; + + + + +// fn main() { +// let mut mail_path = dirs::home_dir().unwrap(); +// mail_path.push(".mail"); + +// let md = MailBox::new(); +// let nmcmd = NotMuchCommand::new(md.path()); + +// match notmuch::Database::open( +// &mail_path.to_str().unwrap().to_string(), +// notmuch::DatabaseMode::ReadOnly, +// ) { +// Ok(db) => { +// #[cfg(feature = "v0_21")] +// { +// let rev = db.revision(); +// println!("db revision: {:?}", rev); +// } +// let query = { +// let dbr = Arc::new(db); + +// notmuch::Query::create(dbr.clone(), &"".to_string()).unwrap() +// }; + +// // let mut threads = query.search_threads().unwrap(); + +// // let mut threads = db.create_query(&"".to_string()).unwrap().search_threads().unwrap(); + +// let mut threads = Arc::new(<Query as QueryExt>::search_threads(query).unwrap()); + +// for thread in Arc::get_mut(&mut threads).unwrap() +// { +// println!("thread {:?} {:?}", thread.subject(), thread.authors()); +// } +// } +// Err(err) => { +// println!("Got error while trying to open db: {:?}", err); +// } +// } +// } + + + + + diff --git a/tests/test_database.rs b/tests/test_database.rs new file mode 100644 index 0000000..96d9bcc --- /dev/null +++ b/tests/test_database.rs @@ -0,0 +1,337 @@ +use fixtures::{NotmuchCommand, MailBox}; + +// #[test] +// // fn test_config_pathname_default(){ + +// // monkeypatch.delenv('NOTMUCH_CONFIG', raising=False) +// // user = pathlib.Path('~/.notmuch-config').expanduser() +// // assert dbmod._config_pathname() == user + +// // } + +mod database { + + use super::*; + + #[test] + fn test_create(){ + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()); + assert!(db.is_ok()); + + assert!(mailbox.path().join(".notmuch/xapian").exists()); + } + + #[test] + fn test_create_already_open(){ + let mailbox = MailBox::new(); + let db1 = notmuch::Database::create(&mailbox.path()); + assert!(db1.is_ok()); + + let db2 = notmuch::Database::create(&mailbox.path()); + assert!(db2.is_err()); + } + + + #[test] + fn test_create_existing(){ + let mailbox = MailBox::new(); + notmuch::Database::create(&mailbox.path()).unwrap(); + + let db2 = notmuch::Database::create(&mailbox.path()); + assert!(db2.is_err()); + } + + + #[test] + fn test_close(){ + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + assert!(db.close().is_ok()); + } + + #[test] + fn test_drop_noclose(){ + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + drop(db); + } + + #[test] + fn test_close_drop(){ + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + db.close().unwrap(); + drop(db); + } + + #[test] + fn test_path(){ + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + assert!(db.path() == mailbox.path()); + } + + #[test] + fn test_version(){ + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + assert!(db.version() > 0); + } + +} + + +mod atomic { + use super::*; + + // TODO: how do I test this?? + +} + + +mod revision { + use super::*; + + #[test] + fn test_single_rev(){ + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + let rev0 = db.revision(); + let rev1 = db.revision(); + + assert!(rev0 == rev1); + assert!(rev0 <= rev1); + assert!(rev0 >= rev1); + assert!(!(rev0 < rev1)); + assert!(!(rev0 > rev1)); + } + + #[test] + fn test_diff_db(){ + let mailbox0 = MailBox::new(); + let db0 = notmuch::Database::create(&mailbox0.path()).unwrap(); + let rev0 = db0.revision(); + + + let mailbox1 = MailBox::new(); + let db1 = notmuch::Database::create(&mailbox1.path()).unwrap(); + let rev1 = db1.revision(); + + assert!(rev0 != rev1); + assert!(rev0.uuid != rev1.uuid); + } + + #[test] + fn test_cmp(){ + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + let rev0 = db.revision(); + + let (_, filename) = mailbox.deliver(None, None, None, None, vec![], true, None, false, false, false).unwrap(); + + db.index_file(&filename, None).unwrap(); + + let rev1 = db.revision(); + + assert!(rev0 < rev1); + assert!(rev0 <= rev1); + assert!(!(rev0 > rev1)); + assert!(!(rev0 >= rev1)); + assert!(!(rev0 == rev1)); + assert!(rev0 != rev1); + + + } + + // TODO: add tests for revisions comparisons + +} + + +mod messages { + use super::*; + + #[test] + fn test_add_message() { + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + let (msgid, filename) = mailbox.deliver(None, None, None, None, vec![], true, None, false, false, false).unwrap(); + let msg = db.index_file(&filename, None).unwrap(); + + assert!(msg.filename() == filename); + assert!(msg.id() == msgid); + + } + + #[test] + fn test_remove_message() { + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + let (msgid, filename) = mailbox.deliver(None, None, None, None, vec![], true, None, false, false, false).unwrap(); + let msg = db.index_file(&filename, None).unwrap(); + assert!(db.find_message(&msgid).unwrap().is_some()); + + db.remove_message(&filename).unwrap(); + assert!(db.find_message(&msgid).unwrap().is_none()); + } + + #[test] + fn test_find_message() { + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + let (msgid, filename) = mailbox.deliver(None, None, None, None, vec![], true, None, false, false, false).unwrap(); + let msg0 = db.index_file(&filename, None).unwrap(); + + let msg1 = db.find_message(&msgid).unwrap().unwrap(); + assert!(msg0.id() == msgid); + assert!(msg0.id() == msg1.id()); + + assert!(msg0.filename() == filename); + assert!(msg0.filename() == msg1.filename()); + } + + #[test] + fn test_find_message_notfound() { + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + assert!(db.find_message(&"foo").unwrap().is_none()); + } + +} + +mod tags { + use super::*; + + #[test] + fn test_none() { + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + let tags = db.all_tags().unwrap(); + + assert!(tags.count() == 0); + } + + #[test] + fn test_some() { + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + let (_, filename) = mailbox.deliver(None, None, None, None, vec![], true, None, false, false, false).unwrap(); + let msg = db.index_file(&filename, None).unwrap(); + + msg.add_tag(&"hello").unwrap(); + let tags: Vec<String> = db.all_tags().unwrap().collect(); + + assert!(tags.len() == 1); + assert!(tags.iter().any(|x| x == "hello")); + } + + #[test] + fn test_iters() { + let mailbox = MailBox::new(); + let db = notmuch::Database::create(&mailbox.path()).unwrap(); + + let t1: Vec<String> = db.all_tags().unwrap().collect(); + let t2: Vec<String> = db.all_tags().unwrap().collect(); + assert!(t1 == t2); + } + +} + +struct PopulatedDatabase { + // Return a read-write Database. + // The database will have 3 messages, 2 threads. + + pub mailbox: MailBox, + pub database: notmuch::Database, +} + +impl PopulatedDatabase { + pub fn new() -> Self{ + let mailbox = MailBox::new(); + + let (msgid, _) = mailbox.deliver(None, Some("foo".to_string()), None, None, vec![], true, None, false, false, false).unwrap(); + mailbox.deliver(None, Some("bar".to_string()), None, None, vec![], true, None, false, false, false).unwrap(); + mailbox.deliver(None, Some("baz".to_string()), None, None, vec![("In-Reply-To".to_string(), format!("<{}>", msgid))], true, None, false, false, false).unwrap(); + + let cmd = NotmuchCommand::new(&mailbox.path()); + cmd.run(vec!["new"]).unwrap(); + + let database = notmuch::Database::open(&mailbox.path(), notmuch::DatabaseMode::ReadWrite).unwrap(); + + Self { + mailbox, + database + } + } +} + +mod query { + use super::*; + + #[test] + fn test_count_messages() { + let db = PopulatedDatabase::new(); + + let query = db.database.create_query("*").unwrap(); + assert!(query.count_messages().unwrap() == 3); + } + + #[test] + fn test_message_no_results() { + let db = PopulatedDatabase::new(); + + let query = db.database.create_query("not_a_matching_query").unwrap(); + let mut messages = query.search_messages().unwrap(); + let msg = messages.next(); + assert!(msg.is_none()); + } + + #[test] + fn test_message_match() { + let db = PopulatedDatabase::new(); + + let query = db.database.create_query("*").unwrap(); + let mut messages = query.search_messages().unwrap(); + let msg = messages.next(); + assert!(msg.is_some()); + } + + #[test] + fn test_count_threads() { + let db = PopulatedDatabase::new(); + + let query = db.database.create_query("*").unwrap(); + assert!(query.count_threads().unwrap() == 2); + } + + #[test] + fn test_threads_no_results() { + let db = PopulatedDatabase::new(); + + let query = db.database.create_query("not_a_matching_query").unwrap(); + let mut threads = query.search_threads().unwrap(); + let thrd = threads.next(); + assert!(thrd.is_none()); + } + + #[test] + fn test_threads_match() { + let db = PopulatedDatabase::new(); + + let query = db.database.create_query("*").unwrap(); + let mut threads = query.search_threads().unwrap(); + let thrd = threads.next(); + assert!(thrd.is_some()); + } +} + |
