Files
ElyOffice/crates/ely-mail-engine/tests/pop_sync.rs
T

195 lines
5.9 KiB
Rust

mod fake;
use std::sync::Mutex;
use ely_mail_engine::{
Account, AccountId, Error, Key, Protocol, Role, Secret, Security, Store, sync,
};
use fake::{account, certs, expect, send, serve};
const NOW: i64 = 1_792_411_200;
fn opened(keep: bool) -> (tempfile::TempDir, Mutex<Store>, Account, AccountId) {
let dir = tempfile::tempdir().unwrap();
let store = Store::open(dir.path()).unwrap();
let account = Account {
protocol: Protocol::Pop { keep },
..account("ann@example.com")
};
let id = store.add_account(&account).unwrap();
(dir, Mutex::new(store), account, id)
}
/// A login, then `steps`.
async fn run(
store: &Mutex<Store>,
account: &Account,
id: AccountId,
steps: Vec<fake::Step>,
) -> Result<sync::Report, Error> {
let certs = certs();
let mut script = vec![
send("+OK ready\n"),
expect("USER ann@example.com"),
send("+OK\n"),
expect("PASS pw"),
send("+OK in\n"),
];
script.extend(steps);
let (incoming, task) = serve(&certs, Security::Tls, script).await;
let account = Account {
incoming,
..account.clone()
};
let secret = Secret::Password("pw".into());
let done = sync::pop(&account, id, secret, &certs.trusted, store, NOW).await;
if done.is_ok() {
task.await.unwrap().unwrap();
}
done
}
fn subjects(store: &Mutex<Store>, id: AccountId) -> Vec<(String, String)> {
let store = store.lock().unwrap();
let [(inbox, name, role)] = store.folders(id).unwrap().try_into().unwrap();
assert_eq!((name.as_str(), role), ("INBOX", Some(Role::Inbox)));
let mut out: Vec<_> = store
.uidls(inbox)
.unwrap()
.into_iter()
.map(|uidl| {
let key = Key::Pop { uidl: uidl.clone() };
let found = store.find(inbox, &key).unwrap().unwrap();
let stored = store.get(found).unwrap();
assert!(stored.body);
(uidl, stored.subject.unwrap())
})
.collect();
out.sort();
out
}
fn quit() -> [fake::Step; 2] {
[expect("QUIT"), send("+OK bye\n")]
}
#[tokio::test]
async fn new_mail_comes_down_once_and_stays_on_the_server() {
let (_dir, store, account, id) = opened(true);
let mut first = vec![
expect("UIDL"),
send("+OK\n1 aa1\n2 bb2\n.\n"),
expect("RETR 1"),
send("+OK\nSubject: One\n\nhi\n.\n"),
expect("RETR 2"),
send("+OK\nSubject: Two\n\nhi\n.\n"),
];
first.extend(quit());
let report = run(&store, &account, id, first).await.unwrap();
assert_eq!((report.bodies, report.deleted), (2, 0));
assert_eq!(store.lock().unwrap().synced(id).unwrap().at, Some(NOW));
let mut second = vec![
expect("UIDL"),
send("+OK\n1 aa1\n2 bb2\n3 cc3\n.\n"),
expect("RETR 3"),
send("+OK\nSubject: Three\n\nhi\n.\n"),
];
second.extend(quit());
let report = run(&store, &account, id, second).await.unwrap();
assert_eq!((report.bodies, report.deleted), (1, 0));
let held = subjects(&store, id);
assert_eq!(
held,
[("aa1", "One"), ("bb2", "Two"), ("cc3", "Three")]
.map(|(uidl, subject)| (uidl.to_owned(), subject.to_owned()))
);
}
#[tokio::test]
async fn without_keep_each_stored_message_is_deleted() {
let (_dir, store, account, id) = opened(false);
let mut first = vec![
expect("UIDL"),
send("+OK\n1 aa1\n.\n"),
expect("RETR 1"),
send("+OK\nSubject: One\n\nhi\n.\n"),
expect("DELE 1"),
send("+OK deleted\n"),
];
first.extend(quit());
run(&store, &account, id, first).await.unwrap();
// A held message the server still lists goes too.
let mut second = vec![
expect("UIDL"),
send("+OK\n1 aa1\n2 bb2\n.\n"),
expect("DELE 1"),
send("+OK deleted\n"),
expect("RETR 2"),
send("+OK\nSubject: Two\n\nhi\n.\n"),
expect("DELE 2"),
send("+OK deleted\n"),
];
second.extend(quit());
let report = run(&store, &account, id, second).await.unwrap();
assert_eq!((report.bodies, report.deleted), (1, 2));
assert_eq!(subjects(&store, id).len(), 2);
}
#[tokio::test]
async fn a_uidl_listed_twice_comes_down_once() {
for keep in [true, false] {
let (_dir, store, account, id) = opened(keep);
let mut steps = vec![
expect("UIDL"),
send("+OK\n1 aa1\n2 aa1\n.\n"),
expect("RETR 1"),
send("+OK\nSubject: One\n\nhi\n.\n"),
];
if !keep {
steps.extend([
expect("DELE 1"),
send("+OK deleted\n"),
expect("DELE 2"),
send("+OK deleted\n"),
]);
}
steps.extend(quit());
let report = run(&store, &account, id, steps).await.unwrap();
let deleted = if keep { 0 } else { 2 };
assert_eq!((report.bodies, report.deleted), (1, deleted));
assert_eq!(subjects(&store, id).len(), 1);
}
}
#[tokio::test]
async fn a_refused_message_stops_the_sync_before_any_delete() {
let (_dir, store, account, id) = opened(false);
let steps = vec![
expect("UIDL"),
send("+OK\n1 aa1\n.\n"),
expect("RETR 1"),
send("-ERR gone\n"),
];
let error = run(&store, &account, id, steps).await.unwrap_err();
assert!(
matches!(error, Error::Server(ref text) if text == "gone"),
"{error}"
);
assert_eq!(store.lock().unwrap().synced(id).unwrap().at, None);
assert!(subjects(&store, id).is_empty());
}
#[tokio::test]
async fn an_imap_account_is_refused() {
let (_dir, store, account, id) = opened(true);
let account = Account {
protocol: Protocol::Imap,
..account
};
let secret = Secret::Password("pw".into());
let error = sync::pop(&account, id, secret, &certs().trusted, &store, NOW)
.await
.unwrap_err();
assert!(matches!(error, Error::Invalid(_)), "{error}");
}