aboutsummaryrefslogtreecommitdiffstats
path: root/notmuch/tests
diff options
context:
space:
mode:
authorGustav Sörnäs <gustav@sornas.net>2021-04-25 16:58:17 +0200
committerGustav Sörnäs <gustav@sornas.net>2021-04-25 16:58:17 +0200
commit72c2b83748a601d02c82d5bcb4220d3b238281cc (patch)
tree38acb17db4a48fc9f340680ea5b86c6cd49d8f2a /notmuch/tests
parent51fa75397dda2c280f29760e7b525caefc03642e (diff)
parent2231a5cf6cdeb90c1cdb75d161a0063d4a385576 (diff)
downloadmail-72c2b83748a601d02c82d5bcb4220d3b238281cc.tar.gz
Add 'notmuch/' from commit '2231a5cf6cdeb90c1cdb75d161a0063d4a385576'
git-subtree-dir: notmuch git-subtree-mainline: 51fa75397dda2c280f29760e7b525caefc03642e git-subtree-split: 2231a5cf6cdeb90c1cdb75d161a0063d4a385576
Diffstat (limited to 'notmuch/tests')
-rw-r--r--notmuch/tests/fixtures.rs198
-rw-r--r--notmuch/tests/lib.rs15
-rw-r--r--notmuch/tests/main.rs62
-rw-r--r--notmuch/tests/test_database.rs339
-rw-r--r--notmuch/tests/test_message.rs291
-rw-r--r--notmuch/tests/test_query.rs97
-rw-r--r--notmuch/tests/test_tags.rs142
-rw-r--r--notmuch/tests/test_thread.rs102
8 files changed, 1246 insertions, 0 deletions
diff --git a/notmuch/tests/fixtures.rs b/notmuch/tests/fixtures.rs
new file mode 100644
index 0000000..7075905
--- /dev/null
+++ b/notmuch/tests/fixtures.rs
@@ -0,0 +1,198 @@
+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::{Result, Write};
+use std::fs::{self, File};
+use std::path::PathBuf;
+use tempfile::{tempdir, TempDir};
+use std::process::Command;
+use maildir::Maildir;
+use lettre_email::{EmailBuilder, Header};
+use lettre::SendableEmail;
+
+
+// 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,
+ 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,
+ 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()
+ }
+
+ /// 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()
+ .subject(subject.unwrap_or_else(|| "Test mail".to_string()));
+
+
+ if let Some(val) = body {
+ builder = builder.text(val);
+ }
+
+ builder = builder.to(to.unwrap_or_else(|| "to@example.com".to_string()))
+ .from(from.unwrap_or_else(|| "src@example.com".to_string()));
+
+ 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";
+ }
+ println!("flags: {:?}", flags);
+ let mid = self.maildir.store_cur_with_flags(&msg.message_to_string().unwrap().as_bytes(), flags.as_str()).unwrap();
+
+ // I have no idea what the reasoning for the :2 here is, but ok.
+ format!("{}:2,{}", mid, flags)
+ };
+
+
+ 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/notmuch/tests/lib.rs b/notmuch/tests/lib.rs
new file mode 100644
index 0000000..44ec82f
--- /dev/null
+++ b/notmuch/tests/lib.rs
@@ -0,0 +1,15 @@
+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;
+mod test_query;
+mod test_thread;
+mod test_message;
+mod test_tags;
+
diff --git a/notmuch/tests/main.rs b/notmuch/tests/main.rs
new file mode 100644
index 0000000..17db2bc
--- /dev/null
+++ b/notmuch/tests/main.rs
@@ -0,0 +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};
+
+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/notmuch/tests/test_database.rs b/notmuch/tests/test_database.rs
new file mode 100644
index 0000000..098e271
--- /dev/null
+++ b/notmuch/tests/test_database.rs
@@ -0,0 +1,339 @@
+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_eq!(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??
+
+}
+
+
+#[cfg(feature = "v0_21")]
+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_ne!(rev0, rev1);
+ assert_ne!(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_eq!(msg.filename(), filename);
+ assert_eq!(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();
+ 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_eq!(msg0.id(), msgid);
+ assert_eq!(msg0.id(), msg1.id());
+
+ assert_eq!(msg0.filename(), filename);
+ assert_eq!(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_eq!(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_eq!(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_eq!(t1, t2);
+ }
+
+}
+
+struct DatabaseFixture {
+ // Return a read-write Database.
+ // The database will have 3 messages, 2 threads.
+
+ pub mailbox: MailBox,
+ pub database: notmuch::Database,
+}
+
+impl DatabaseFixture {
+ 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 = DatabaseFixture::new();
+
+ let query = db.database.create_query("*").unwrap();
+ assert_eq!(query.count_messages().unwrap(), 3);
+ }
+
+ #[test]
+ fn test_message_no_results() {
+ let db = DatabaseFixture::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 = DatabaseFixture::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 = DatabaseFixture::new();
+
+ let query = db.database.create_query("*").unwrap();
+ assert_eq!(query.count_threads().unwrap(), 2);
+ }
+
+ #[test]
+ fn test_threads_no_results() {
+ let db = DatabaseFixture::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 = DatabaseFixture::new();
+
+ let query = db.database.create_query("*").unwrap();
+ let mut threads = query.search_threads().unwrap();
+ let thrd = threads.next();
+ assert!(thrd.is_some());
+ }
+}
+
diff --git a/notmuch/tests/test_message.rs b/notmuch/tests/test_message.rs
new file mode 100644
index 0000000..7af6f2c
--- /dev/null
+++ b/notmuch/tests/test_message.rs
@@ -0,0 +1,291 @@
+use std::sync::Arc;
+use std::path::PathBuf;
+use fixtures::MailBox;
+
+struct MessageFixture {
+ // Return a single thread with 2 messages
+ pub mailbox: MailBox,
+ pub database: Arc<notmuch::Database>,
+ pub maildir_msg: (String, PathBuf),
+ pub message: notmuch::Message<'static, notmuch::Database>,
+}
+
+impl MessageFixture {
+ pub fn new() -> Self{
+ let mailbox = MailBox::new();
+
+ let (msgid, filename) = mailbox.deliver(None, None, None, None, vec![], true, None, false, false, false).unwrap();
+
+ let database = Arc::new(notmuch::Database::create(&mailbox.path()).unwrap());
+ let message = <notmuch::Database as notmuch::DatabaseExt>::index_file(database.clone(), &filename, None).unwrap();
+
+ Self {
+ mailbox,
+ database,
+ maildir_msg: (msgid, filename),
+ message
+ }
+ }
+}
+
+mod message {
+
+ use super::*;
+
+ #[test]
+ fn test_messageid() {
+ let msg = MessageFixture::new();
+ let copy = <notmuch::Database as notmuch::DatabaseExt>::find_message_by_filename(msg.database.clone(), &msg.message.filename()).unwrap().unwrap();
+ assert_eq!(msg.message.id(), copy.id())
+ }
+
+ #[test]
+ fn test_messageid_find() {
+ let msg = MessageFixture::new();
+ let copy = <notmuch::Database as notmuch::DatabaseExt>::find_message(msg.database.clone(), &msg.message.id()).unwrap().unwrap();
+ assert_eq!(msg.message.id(), copy.id())
+ }
+
+ #[test]
+ fn test_path() {
+ let msg = MessageFixture::new();
+ assert_eq!(msg.message.filename(), msg.maildir_msg.1)
+ }
+
+
+ #[test]
+ fn test_filenames() {
+ let msg = MessageFixture::new();
+ let mut filenames = msg.message.filenames();
+ let filename = filenames.next().unwrap();
+
+ assert_eq!(filename, msg.message.filename());
+
+ assert!(filenames.next().is_none());
+ let names: Vec<PathBuf> = msg.message.filenames().collect();
+
+ assert_eq!(names, vec![msg.maildir_msg.1]);
+ }
+
+ #[test]
+ fn test_header() {
+ let msg = MessageFixture::new();
+ assert_eq!(msg.message.header(&"from").unwrap().unwrap().to_string(), "<src@example.com>");
+ }
+
+ #[test]
+ fn test_header_not_present() {
+ let msg = MessageFixture::new();
+ assert_eq!(msg.message.header(&"foo").unwrap(), None);
+ }
+
+ #[test]
+ fn test_freeze() {
+ let msg = MessageFixture::new();
+
+ msg.message.freeze().unwrap();
+ msg.message.add_tag(&"foo").unwrap();
+ msg.message.add_tag(&"bar").unwrap();
+ msg.message.remove_tag(&"foo").unwrap();
+ msg.message.thaw().unwrap();
+
+ assert!(msg.message.tags().all(|x| x != "foo"));
+ assert!(msg.message.tags().any(|x| x == "bar"));
+ }
+
+ #[test]
+ fn test_freeze_context() {
+ let msg = MessageFixture::new();
+
+ {
+ let _frozen = notmuch::FrozenMessage::new(&msg.message).unwrap();
+ msg.message.add_tag(&"foo").unwrap();
+ msg.message.add_tag(&"bar").unwrap();
+ msg.message.remove_tag(&"foo").unwrap();
+
+ }
+ assert!(msg.message.tags().all(|x| x != "foo"));
+ assert!(msg.message.tags().any(|x| x == "bar"));
+ }
+
+
+ #[test]
+ fn test_freeze_err() {
+ // not sure if this test is ok?
+ let msg = MessageFixture::new();
+
+ msg.message.add_tag(&"foo").unwrap();
+
+ msg.message.freeze().unwrap();
+ assert!(msg.message.remove_all_tags().is_ok());
+
+ let copy = <notmuch::Database as notmuch::DatabaseExt>::find_message(msg.database.clone(), &msg.message.id()).unwrap().unwrap();
+ assert!(copy.tags().any(|x| x == "foo"));
+
+ msg.message.thaw().unwrap();
+
+ assert!(!msg.message.tags().any(|x| x == "foo"));
+
+ let copy2 = <notmuch::Database as notmuch::DatabaseExt>::find_message(msg.database.clone(), &msg.message.id()).unwrap().unwrap();
+ assert!(!copy2.tags().any(|x| x == "foo"));
+ }
+
+ #[test]
+ fn test_freeze_context_err() {
+ // not sure if this test is ok?
+ let msg = MessageFixture::new();
+ msg.message.add_tag(&"foo").unwrap();
+
+ {
+ let _frozen = notmuch::FrozenMessage::new(&msg.message).unwrap();
+ assert!(msg.message.remove_all_tags().is_ok());
+ assert!(!msg.message.tags().any(|x| x == "foo"));
+
+ let copy = <notmuch::Database as notmuch::DatabaseExt>::find_message(msg.database.clone(), &msg.message.id()).unwrap().unwrap();
+ assert!(copy.tags().any(|x| x == "foo"));
+ }
+
+ let copy2 = <notmuch::Database as notmuch::DatabaseExt>::find_message(msg.database.clone(), &msg.message.id()).unwrap().unwrap();
+ assert!(!copy2.tags().any(|x| x == "foo"));
+ assert!(!msg.message.tags().any(|x| x == "foo"));
+ }
+
+ #[test]
+ fn test_replies() {
+ let msg = MessageFixture::new();
+ assert_eq!(msg.message.replies().count(), 0);
+ }
+
+}
+
+
+// def test_date(self, msg):
+// # XXX Someone seems to treat things as local time instead of
+// # UTC or the other way around.
+// now = int(time.time())
+// assert abs(now - msg.date) < 3600*24
+
+mod properties {
+ use super::*;
+
+ #[test]
+ fn test_add_single() {
+ let msg = MessageFixture::new();
+ msg.message.add_property(&"foo", &"bar").unwrap();
+ assert_eq!(msg.message.property(&"foo").unwrap(), "bar");
+
+ msg.message.add_property(&"bar", &"baz").unwrap();
+ assert_eq!(msg.message.property(&"bar").unwrap(), "baz");
+ }
+
+ #[test]
+ fn test_add_dup() {
+ let msg = MessageFixture::new();
+ msg.message.add_property(&"foo", &"bar").unwrap();
+ msg.message.add_property(&"foo", &"baz").unwrap();
+
+ assert_eq!(msg.message.property(&"foo").unwrap(), "bar");
+
+ let props = msg.message.properties(&"foo", true);
+ let expect = vec![("foo", "bar"), ("foo", "baz")];
+ for (&(ek, ev), (pk, pv)) in expect.iter().zip(props) {
+ assert_eq!(ek, pk);
+ assert_eq!(ev, pv);
+ }
+ }
+
+ #[test]
+ fn test_len() {
+ let msg = MessageFixture::new();
+ msg.message.add_property(&"foo", &"a").unwrap();
+ msg.message.add_property(&"foo", &"b").unwrap();
+ msg.message.add_property(&"bar", &"a").unwrap();
+
+ let num_props = msg.message.properties(&"", false).count();
+ assert_eq!(num_props, 3);
+
+ let mut prop_keys: Vec<String> = msg.message.properties(&"", false).map(|x| x.0).collect();
+ prop_keys.sort();
+ prop_keys.dedup();
+ assert_eq!(prop_keys.len(), 2);
+
+ let mut prop_vals: Vec<String> = msg.message.properties(&"", false).map(|x| x.1).collect();
+ prop_vals.sort();
+ prop_vals.dedup();
+ assert_eq!(prop_vals.len(), 2);
+ }
+
+ #[test]
+ fn test_del() {
+ let msg = MessageFixture::new();
+ msg.message.add_property(&"foo", &"a").unwrap();
+ msg.message.add_property(&"foo", &"b").unwrap();
+
+ msg.message.remove_all_properties(Some(&"foo")).unwrap();
+ assert!(msg.message.property(&"foo").is_err());
+ }
+
+ #[test]
+ fn test_remove() {
+ let msg = MessageFixture::new();
+ msg.message.add_property(&"foo", &"a").unwrap();
+ msg.message.add_property(&"foo", &"b").unwrap();
+
+ msg.message.remove_property(&"foo", &"a").unwrap();
+ assert_eq!(msg.message.property(&"foo").unwrap(), "b");
+ }
+
+ #[test]
+ fn test_clear() {
+ let msg = MessageFixture::new();
+ msg.message.add_property(&"foo", &"a").unwrap();
+
+ msg.message.remove_all_properties(None).unwrap();
+ assert!(msg.message.property(&"foo").is_err());
+ }
+
+ #[test]
+ fn test_getall() {
+ let msg = MessageFixture::new();
+ msg.message.add_property(&"foo", &"a").unwrap();
+
+ let prop_keys: Vec<String> = msg.message.properties(&"foo", false).map(|x| x.0).collect();
+ assert_eq!(prop_keys.len(), 1);
+ assert_eq!(prop_keys, vec!["foo"]);
+
+ let prop_vals: Vec<String> = msg.message.properties(&"foo", false).map(|x| x.1).collect();
+ assert_eq!(prop_vals.len(), 1);
+ assert_eq!(prop_vals, vec!["a"]);
+ }
+
+ #[test]
+ fn test_getall_prefix() {
+ let msg = MessageFixture::new();
+ msg.message.add_property(&"foo", &"a").unwrap();
+ msg.message.add_property(&"foobar", &"b").unwrap();
+
+ let prop_keys: Vec<String> = msg.message.properties(&"foo", false).map(|x| x.0).collect();
+ assert_eq!(prop_keys.len(), 2);
+ assert_eq!(prop_keys, vec!["foo", "foobar"]);
+
+ let prop_vals: Vec<String> = msg.message.properties(&"foo", false).map(|x| x.1).collect();
+ assert_eq!(prop_vals.len(), 2);
+ assert_eq!(prop_vals, vec!["a", "b"]);
+ }
+
+ #[test]
+ fn test_getall_exact() {
+ let msg = MessageFixture::new();
+ msg.message.add_property(&"foo", &"a").unwrap();
+ msg.message.add_property(&"foobar", &"b").unwrap();
+
+ let prop_keys: Vec<String> = msg.message.properties(&"foo", true).map(|x| x.0).collect();
+ assert_eq!(prop_keys.len(), 1);
+ assert_eq!(prop_keys, vec!["foo"]);
+
+ let prop_vals: Vec<String> = msg.message.properties(&"foo", true).map(|x| x.1).collect();
+ assert_eq!(prop_vals.len(), 1);
+ assert_eq!(prop_vals, vec!["a"]);
+ }
+}
+
diff --git a/notmuch/tests/test_query.rs b/notmuch/tests/test_query.rs
new file mode 100644
index 0000000..ad8f299
--- /dev/null
+++ b/notmuch/tests/test_query.rs
@@ -0,0 +1,97 @@
+use std::sync::Arc;
+use fixtures::{NotmuchCommand, MailBox};
+
+
+struct QueryFixture {
+ // Return a single thread with 2 messages
+ pub mailbox: MailBox,
+ pub query: notmuch::Query<'static>,
+}
+
+impl QueryFixture {
+ 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();
+ mailbox.deliver(None, Some("foo qux".to_string()), None, None, vec![], true, None, false, false, false).unwrap();
+ mailbox.deliver(None, Some("foo quux".to_string()), None, None, vec![], true, None, false, false, false).unwrap();
+
+ let cmd = NotmuchCommand::new(&mailbox.path());
+ cmd.run(vec!["new"]).unwrap();
+
+ let query = {
+ let database = Arc::new(notmuch::Database::open(&mailbox.path(), notmuch::DatabaseMode::ReadWrite).unwrap());
+
+ notmuch::Query::create(database, &"foo".to_string()).unwrap()
+ };
+
+ Self {
+ mailbox,
+ query
+ }
+ }
+}
+
+#[test]
+fn test_iter_threads() {
+ let q = QueryFixture::new();
+
+ let threads = q.query.search_threads().unwrap();
+
+ let mut num = 0;
+ for _thread in threads {
+ num += 1;
+ }
+
+ assert_eq!(num, 3);
+
+}
+
+#[test]
+fn test_iter_threads_ext() {
+ let q = QueryFixture::new();
+
+ let threads = <notmuch::Query as notmuch::QueryExt>::search_threads(q.query).unwrap();
+
+ let mut num = 0;
+ for _thread in threads {
+ num += 1;
+ }
+
+ assert_eq!(num, 3);
+
+}
+
+
+#[test]
+fn test_iter_messages() {
+ let q = QueryFixture::new();
+
+ let messages = q.query.search_messages().unwrap();
+
+ let mut num = 0;
+ for _message in messages {
+ num += 1;
+ }
+
+ assert_eq!(num, 3);
+
+}
+
+#[test]
+fn test_iter_messages_ext() {
+ let q = QueryFixture::new();
+
+ let messages = <notmuch::Query as notmuch::QueryExt>::search_messages(q.query).unwrap();
+
+ let mut num = 0;
+ for _message in messages {
+ num += 1;
+ }
+
+ assert_eq!(num, 3);
+
+}
+
diff --git a/notmuch/tests/test_tags.rs b/notmuch/tests/test_tags.rs
new file mode 100644
index 0000000..3d39486
--- /dev/null
+++ b/notmuch/tests/test_tags.rs
@@ -0,0 +1,142 @@
+use std::sync::Arc;
+use fixtures::{MailBox, NotmuchCommand};
+
+struct TagSetFixture {
+ // An non-empty immutable tagset.
+ // This will have the default new mail tags: inbox, unread.
+ pub mailbox: MailBox,
+ pub cmd: NotmuchCommand,
+ pub database: Arc<notmuch::Database>,
+ pub message: notmuch::Message<'static, notmuch::Database>
+}
+
+impl TagSetFixture {
+ pub fn new(mutable: bool, flagged: bool) -> Self{
+ let mailbox = MailBox::new();
+ let (_msg, filename) = mailbox.deliver(None, None, None, None, vec![], !flagged, None, false, false, flagged).unwrap();
+
+ let cmd = NotmuchCommand::new(&mailbox.path());
+ cmd.run(vec!["new"]).unwrap();
+
+ let database = Arc::new(notmuch::Database::open(&mailbox.path(), if !mutable {notmuch::DatabaseMode::ReadOnly} else { notmuch::DatabaseMode::ReadWrite }).unwrap());
+ let message = <notmuch::Database as notmuch::DatabaseExt>::find_message_by_filename(database.clone(), &filename).unwrap().unwrap();
+
+ Self {
+ mailbox,
+ database,
+ cmd,
+ message
+ }
+ }
+}
+
+mod immutable {
+
+ use super::*;
+
+ #[test]
+ fn test_neg(){
+ let tagset = TagSetFixture::new(false, false);
+
+ let tags: Vec<String> = tagset.database.all_tags().unwrap().collect();
+ tagset.cmd.run(vec!["tag", "+foo", "*"]).unwrap();
+
+ let database = notmuch::Database::open(&tagset.mailbox.path(), notmuch::DatabaseMode::ReadOnly).unwrap();
+ let ntags: Vec<String> = database.all_tags().unwrap().collect();
+
+ assert_ne!(tags, ntags);
+ }
+
+ #[test]
+ fn test_contains(){
+ let tagset = TagSetFixture::new(false, false);
+ let tags: Vec<String> = tagset.database.all_tags().unwrap().collect();
+
+ assert!(tags.iter().any(|x| x == "unread"));
+ assert!(!tags.iter().any(|x| x == "foo"));
+ }
+
+
+ #[test]
+ fn test_len(){
+ let tagset = TagSetFixture::new(false, false);
+ assert_eq!(tagset.database.all_tags().unwrap().count(), 2);
+ }
+
+}
+
+mod mutable {
+
+ use super::*;
+
+ #[test]
+ fn test_add(){
+ let tagset = TagSetFixture::new(true, false);
+ assert!(!tagset.message.tags().any(|x| x == "foo"));
+
+ tagset.message.add_tag("foo").unwrap();
+ assert!(tagset.message.tags().any(|x| x == "foo"));
+ }
+
+ #[test]
+ fn test_discard(){
+ let tagset = TagSetFixture::new(true, false);
+ assert!(tagset.message.tags().any(|x| x == "inbox"));
+
+ tagset.message.remove_tag("inbox").unwrap();
+ assert!(!tagset.message.tags().any(|x| x == "unbox"));
+ }
+
+ #[test]
+ fn test_discard_not_present(){
+ let tagset = TagSetFixture::new(true, false);
+ assert!(!tagset.message.tags().any(|x| x == "foo"));
+
+ tagset.message.remove_tag("foo").unwrap();
+ }
+
+ #[test]
+ fn test_clear(){
+ let tagset = TagSetFixture::new(true, false);
+ assert!(tagset.message.tags().count() > 0);
+ tagset.message.remove_all_tags().unwrap();
+
+ assert!(tagset.message.tags().count() == 0);
+ }
+
+ #[test]
+ fn test_from_maildir_flags(){
+ let tagset = TagSetFixture::new(true, true);
+
+ tagset.message.remove_tag(&"flagged").unwrap();
+ tagset.message.maildir_flags_to_tags().unwrap();
+
+ assert!(tagset.message.tags().any(|x| x == "flagged"));
+ }
+
+
+ #[test]
+ fn test_to_maildir_flags(){
+
+ let tagset = TagSetFixture::new(true, true);
+
+ let filename = tagset.message.filename();
+ let filestr = filename.to_string_lossy();
+
+ let file_parts: Vec<&str> = filestr.split(',').collect();
+ let flags = file_parts.last().unwrap();
+ println!("Flags {:?}", flags);
+
+ assert!(flags.contains('F'));
+ tagset.message.remove_tag(&"flagged").unwrap();
+ tagset.message.tags_to_maildir_flags().unwrap();
+
+ let filename = tagset.message.filename();
+ let filestr = filename.to_string_lossy();
+
+ let file_parts: Vec<&str> = filestr.split(',').collect();
+ let flags = file_parts.last().unwrap();
+ assert!(!flags.contains('F'));
+ }
+
+} \ No newline at end of file
diff --git a/notmuch/tests/test_thread.rs b/notmuch/tests/test_thread.rs
new file mode 100644
index 0000000..89f1ea0
--- /dev/null
+++ b/notmuch/tests/test_thread.rs
@@ -0,0 +1,102 @@
+use std::sync::Arc;
+use fixtures::{NotmuchCommand, MailBox};
+
+
+struct ThreadFixture {
+ // Return a single thread with 2 messages
+ pub mailbox: MailBox,
+ pub thread: notmuch::Thread<'static, 'static>,
+}
+
+impl ThreadFixture {
+ 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![("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 mut threads = {
+ let database = Arc::new(notmuch::Database::open(&mailbox.path(), notmuch::DatabaseMode::ReadWrite).unwrap());
+
+ let query = notmuch::Query::create(database.clone(), &"foo".to_string()).unwrap();
+
+ <notmuch::Query as notmuch::QueryExt>::search_threads(query).unwrap()
+ };
+ let thread = threads.next().unwrap();
+
+ Self {
+ mailbox,
+ thread
+ }
+ }
+}
+
+#[test]
+fn test_threadid() {
+ let thread = ThreadFixture::new();
+ assert!(!thread.thread.id().is_empty());
+}
+
+
+#[test]
+fn test_toplevel() {
+ let thread = ThreadFixture::new();
+ let msgs = thread.thread.toplevel_messages();
+
+ assert_eq!(msgs.count(), 1);
+}
+
+
+#[test]
+fn test_toplevel_reply() {
+ let thread = ThreadFixture::new();
+ let msg = thread.thread.toplevel_messages().next().unwrap();
+
+ assert_eq!(msg.replies().count(), 1);
+}
+
+#[test]
+fn test_iter() {
+ let thread = ThreadFixture::new();
+ let msg_count0 = thread.thread.messages().count() as i32;
+ let msg_count1 = thread.thread.total_messages();
+
+ assert_eq!(msg_count0, msg_count1);
+}
+
+#[test]
+fn test_matched() {
+ let thread = ThreadFixture::new();
+ assert_eq!(thread.thread.matched_messages(), 1);
+}
+
+
+#[test]
+fn test_authors() {
+ let thread = ThreadFixture::new();
+
+ assert_eq!(thread.thread.authors(), vec!["src@example.com".to_string()]);
+}
+
+
+#[test]
+fn test_subject() {
+ let thread = ThreadFixture::new();
+
+ println!("{:?}", thread.thread.subject());
+ assert_eq!(thread.thread.subject(), "Test mail");
+}
+
+
+
+#[test]
+fn test_tags() {
+ let thread = ThreadFixture::new();
+
+ let tags: Vec<String> = thread.thread.tags().collect();
+ assert!(tags.iter().any(|x| x == "inbox"));
+}
+ \ No newline at end of file