From c393fd71620a544680448bb6a90816395e38048a Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Thu, 22 Nov 2018 18:00:55 -0500 Subject: [PATCH] Add Travis integration testing This uses [GreenMail's Docker image](http://www.icegreen.com/greenmail/#deploy_docker_standalone) to spin up a real SMTP+IMAP server on Travis, and then runs a series of integration tests against it by sending e-mails using [`lettre`](https://crates.io/crates/lettre) and checking that we can receive them correctly. A start on #101. --- .travis.yml | 7 ++ Cargo.toml | 8 ++ tests/integration.rs | 215 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 tests/integration.rs diff --git a/.travis.yml b/.travis.yml index 6c51b8c1..11e50b20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,9 @@ addons: packages: - libssl-dev +services: +- docker + os: - linux - osx @@ -23,6 +26,10 @@ script: - cargo check --all-targets - cargo test +before_install: + - docker pull greenmail/standalone:1.5.8 + - docker run -t -i -e GREENMAIL_OPTS='-Dgreenmail.setup.test.all -Dgreenmail.hostname=0.0.0.0 -Dgreenmail.auth.disabled -Dgreenmail.verbose' -p 3025:3025 -p 3110:3110 -p 3143:3143 -p 3465:3465 -p 3993:3993 -p 3995:3995 greenmail/standalone:1.5.8 + after_success: - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh); diff --git a/Cargo.toml b/Cargo.toml index 8142df95..8f6b7f91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,11 @@ bufstream = "0.1" imap-proto = "0.6" nom = "4.0" base64 = "0.10" + +[dev-dependencies] +lettre = "0.9" +lettre_email = "0.9" + +[patch.crates-io] +lettre = { git = "https://github.com/lettre/lettre.git" } +lettre_email = { git = "https://github.com/lettre/lettre.git" } diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 00000000..b92a7bc1 --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,215 @@ +extern crate imap; +extern crate lettre; +extern crate lettre_email; +extern crate native_tls; + +use lettre::Transport; +use std::net::TcpStream; + +fn tls() -> native_tls::TlsConnector { + native_tls::TlsConnector::builder() + .danger_accept_invalid_certs(true) + .build() + .unwrap() +} + +fn session(user: &str) -> imap::Session> { + let mut s = imap::connect("127.0.0.1:3993", "imap.example.com", &tls()) + .unwrap() + .login(user, user) + .unwrap(); + s.debug = true; + s +} + +fn smtp(user: &str) -> lettre::SmtpTransport { + let creds = lettre::smtp::authentication::Credentials::new(user.to_string(), user.to_string()); + lettre::SmtpClient::new( + "127.0.0.1:3465", + lettre::ClientSecurity::Wrapper(lettre::ClientTlsParameters { + connector: tls(), + domain: "smpt.example.com".to_string(), + }), + ) + .unwrap() + .credentials(creds) + .transport() +} + +#[test] +fn connect_insecure() { + imap::connect_insecure("127.0.0.1:3143").unwrap(); +} + +#[test] +#[ignore] +fn connect_insecure_then_secure() { + // ignored because of https://github.com/greenmail-mail-test/greenmail/issues/135 + imap::connect_insecure("127.0.0.1:3143") + .unwrap() + .secure("imap.example.com", &tls()) + .unwrap(); +} + +#[test] +fn connect_secure() { + imap::connect("127.0.0.1:3993", "imap.example.com", &tls()).unwrap(); +} + +#[test] +fn login() { + session("readonly-test@localhost"); +} + +#[test] +fn logout() { + let mut s = session("readonly-test@localhost"); + s.logout().unwrap(); +} + +#[test] +#[ignore] +fn inbox_zero() { + // https://github.com/djc/tokio-imap/issues/34 + let mut s = session("readonly-test@localhost"); + s.select("INBOX").unwrap(); + let inbox = s.search("ALL").unwrap(); + assert_eq!(inbox.len(), 0); +} + +#[test] +fn inbox() { + let to = "inbox@localhost"; + + // first log in so we'll see the unsolicited e-mails + let mut c = session(to); + c.select("INBOX").unwrap(); + + // then send the e-mail + let mut s = smtp(to); + let e = lettre_email::Email::builder() + .from("sender@localhost") + .to(to) + .subject("My first e-mail") + .text("Hello world from SMTP") + .build() + .unwrap(); + s.send(e.into()).unwrap(); + + // now we should see the e-mail! + let inbox = c.search("ALL").unwrap(); + // and the one message should have the first message sequence number + assert_eq!(inbox.len(), 1); + assert!(inbox.contains(&1)); + + // we should also get two unsolicited responses: Exists and Recent + c.noop().unwrap(); + let mut unsolicited = Vec::new(); + while let Ok(m) = c.unsolicited_responses.try_recv() { + unsolicited.push(m); + } + assert_eq!(unsolicited.len(), 2); + assert!(unsolicited + .iter() + .any(|m| m == &imap::types::UnsolicitedResponse::Exists(1))); + assert!(unsolicited + .iter() + .any(|m| m == &imap::types::UnsolicitedResponse::Recent(1))); + + // let's see that we can also fetch the e-mail + let fetch = c.fetch("1", "(ALL UID)").unwrap(); + assert_eq!(fetch.len(), 1); + let fetch = &fetch[0]; + assert_eq!(fetch.message, 1); + assert_ne!(fetch.uid, None); + assert_eq!(fetch.size, Some(138)); + let e = fetch.envelope().unwrap(); + assert_eq!(e.subject, Some("My first e-mail")); + assert_ne!(e.from, None); + assert_eq!(e.from.as_ref().unwrap().len(), 1); + let from = &e.from.as_ref().unwrap()[0]; + assert_eq!(from.mailbox, Some("sender")); + assert_eq!(from.host, Some("localhost")); + assert_ne!(e.to, None); + assert_eq!(e.to.as_ref().unwrap().len(), 1); + let to = &e.to.as_ref().unwrap()[0]; + assert_eq!(to.mailbox, Some("inbox")); + assert_eq!(to.host, Some("localhost")); + + // and let's delete it to clean up + c.store("1", "+FLAGS (\\Deleted)").unwrap(); + c.expunge().unwrap(); + + // the e-mail should be gone now + // TODO: https://github.com/djc/tokio-imap/issues/34 + // let inbox = c.search("ALL").unwrap(); + // assert_eq!(inbox.len(), 0); +} + +#[test] +fn inbox_uid() { + let to = "inbox-uid@localhost"; + + // first log in so we'll see the unsolicited e-mails + let mut c = session(to); + c.select("INBOX").unwrap(); + + // then send the e-mail + let mut s = smtp(to); + let e = lettre_email::Email::builder() + .from("sender@localhost") + .to(to) + .subject("My first e-mail") + .text("Hello world from SMTP") + .build() + .unwrap(); + s.send(e.into()).unwrap(); + + // now we should see the e-mail! + let inbox = c.uid_search("ALL").unwrap(); + // and the one message should have the first message sequence number + assert_eq!(inbox.len(), 1); + let uid = inbox.into_iter().next().unwrap(); + + // we should also get two unsolicited responses: Exists and Recent + c.noop().unwrap(); + let mut unsolicited = Vec::new(); + while let Ok(m) = c.unsolicited_responses.try_recv() { + unsolicited.push(m); + } + assert_eq!(unsolicited.len(), 2); + assert!(unsolicited + .iter() + .any(|m| m == &imap::types::UnsolicitedResponse::Exists(1))); + assert!(unsolicited + .iter() + .any(|m| m == &imap::types::UnsolicitedResponse::Recent(1))); + + // let's see that we can also fetch the e-mail + let fetch = c.uid_fetch(format!("{}", uid), "(ALL UID)").unwrap(); + assert_eq!(fetch.len(), 1); + let fetch = &fetch[0]; + assert_eq!(fetch.uid, Some(uid)); + let e = fetch.envelope().unwrap(); + assert_eq!(e.subject, Some("My first e-mail")); + + // and let's delete it to clean up + c.uid_store(format!("{}", uid), "+FLAGS (\\Deleted)") + .unwrap(); + c.expunge().unwrap(); + + // the e-mail should be gone now + // TODO: https://github.com/djc/tokio-imap/issues/34 + // let inbox = c.search("ALL").unwrap(); + // assert_eq!(inbox.len(), 0); +} + +#[test] +fn list() { + let mut s = session("readonly-test@localhost"); + s.select("INBOX").unwrap(); + let subdirs = s.list(None, Some("%")).unwrap(); + assert_eq!(subdirs.len(), 0); + + // TODO: make a subdir +}