extern crate chrono; extern crate imap; extern crate lettre; extern crate memory_logger; // extern crate native_tls; use imap::ImapConnection; use tempfile::tempdir; use memory_logger::blocking::MemoryLogger; use std::io; use vomit_sync::*; const USER: &str = "vsync@localhost"; fn test_host() -> String { std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string()) } fn test_imaps_port() -> u16 { std::env::var("TEST_IMAPS_PORT") .unwrap_or("3143".to_string()) .parse() .unwrap_or(3143) } fn clean_mailbox(session: &mut imap::Session) { session.select("INBOX").unwrap(); let inbox = session.search("ALL").unwrap(); if !inbox.is_empty() { session.uid_store("1:*", "+FLAGS (\\Deleted)").unwrap(); } session.expunge().unwrap(); } fn session(user: &str) -> imap::Session> { let host = test_host(); let mut s = imap::ClientBuilder::new(&host, test_imaps_port()) .mode(imap::ConnectionMode::Plaintext) .connect() .unwrap() .login(user, user) .unwrap(); s.debug = true; clean_mailbox(&mut s); s } #[test] fn basic_sync() { // Can be run against // `docker run -it --rm -p 3025:25 -p 3110:110 -p 3143:143 -p 3465:465 -p 3993:993 outoforder/cyrus-imapd-tester:latest` let logger = MemoryLogger::setup(log::Level::Trace).unwrap(); let dir = tempdir().unwrap(); let tmp_path = dir.path().to_owned(); assert!(tmp_path.exists()); let maildir = maildir::Maildir::from(tmp_path); maildir.create_dirs().unwrap(); assert_eq!(maildir.count_new(), 0); let mbox = "INBOX"; let mut c = session(USER); c.run_command_and_read_response("ENABLE QRESYNC").unwrap(); // make a message to append let e: lettre::Message = lettre::message::Message::builder() .from("sender@localhost".parse().unwrap()) .to(USER.parse().unwrap()) .subject("My second e-mail") .body("Hello world".to_string()) .unwrap() .into(); // append message c.append(mbox, &e.formatted()).finish().unwrap(); let opts = SyncOptions { // The local maildir to sync to local: String::from(maildir.path().to_str().unwrap()), // The IMAPS URL to sync from remote: String::from(format!("{}:{}", test_host(), test_imaps_port())), // The user for IMAP authentication user: String::from(USER), // The password for IMAP authentication password: String::from(USER), // The number of threads to use threads: 1, // Disable TLS certificate checks (e.g. for self-signed certs) unsafe_tls: true, // Disable TLS completely (insecure) disable_tls: true, // Actually perform the actions list_mailbox_actions: false, // A list of wildcard patterns to include only folders that match any of them. include: Vec::new(), // A list of wildcard patterns to exclude all folders that match any of them. exclude: Vec::new(), // Confirm execution of potentially dangerous actions (e.g. deleting mailboxes) force: true, }; // Initial pull to get some local state pull(&opts).unwrap(); let logs = logger.read(); let text: &str = &logs; println!("{}", text); assert!(logs.contains("Discarding local state")); assert!(logs.contains("forces full resync")); assert!(logs.contains("full fetch for INBOX")); drop(logs); logger.clear(); // Do another pull to verify that nothing happens pull(&opts).unwrap(); let logs = logger.read(); let text: &str = &logs; println!("{}", text); assert!(logs.contains("Skipping INBOX because HIGHESTMODSEQ is in sync")); drop(logs); logger.clear(); let maildir = maildir::Maildir::from(dir.path().to_owned()); assert_eq!(maildir.count_new() + maildir.count_cur(), 1); // Compare local to remote let inbox = c.uid_search("ALL").unwrap(); assert_eq!(inbox.len(), 1); // delete email remotely clean_mailbox(&mut c); // the e-mail should be gone now let inbox = c.search("ALL").unwrap(); assert_eq!(inbox.len(), 0); // A push must bring it back push(&opts).unwrap(); let logs = logger.read(); let text: &str = &logs; println!("{}", text); assert!(logs.contains("restoring mail deleted on remote")); assert!(logs.contains("uploading local mail")); drop(logs); logger.clear(); // Another push should do nothing push(&opts).unwrap(); let logs = logger.read(); let text: &str = &logs; println!("{}", text); assert!(logs.contains("Skipping INBOX because HIGHESTMODSEQ is in sync")); drop(logs); logger.clear(); // Assert we didn't delete anything assert_eq!(maildir.count_new() + maildir.count_cur(), 1); // Assert it's back in mailbox let inbox = c.search("ALL").unwrap(); assert_eq!(inbox.len(), 1); // Store a new mail locally let mail_id = maildir.store_cur_with_flags(&e.formatted(), "S").unwrap(); // and add "another" one remotely c.append(mbox, &e.formatted()).finish().unwrap(); // sync both ways sync(&opts).unwrap(); let logs = logger.read(); let text: &str = &logs; println!("{}", text); drop(logs); logger.clear(); // another sync should do nothing sync(&opts).unwrap(); let logs = logger.read(); let text: &str = &logs; println!("{}", text); assert!(logs.contains("Skipping INBOX because HIGHESTMODSEQ is in sync")); drop(logs); logger.clear(); // There should be three everywhere now assert_eq!(maildir.count_new() + maildir.count_cur(), 3); let inbox = c.uid_search("ALL").unwrap(); assert_eq!(inbox.len(), 3); // Delete a mail locally maildir.delete(&mail_id).unwrap(); // another sync should delete it remotely sync(&opts).unwrap(); let logs = logger.read(); let text: &str = &logs; println!("{}", text); assert!(logs.contains("deleting remote UID")); drop(logs); logger.clear(); // There should be two everywhere now assert_eq!(maildir.count_new() + maildir.count_cur(), 2); let inbox = c.uid_search("ALL").unwrap(); assert_eq!(inbox.len(), 2); }