use std::collections::VecDeque;
use io_imap::{
client::{ImapClientAsync, ImapClientError},
codec::fragmentizer::Fragmentizer,
coroutine::{ImapCoroutine, ImapCoroutineState, ImapYield},
};
const FRAGMENTIZER_MAX_MESSAGE_SIZE: u32 = 1024 * 1024;
struct FakeClientAsync {
fragmentizer: Fragmentizer,
replies: VecDeque<String>,
tag: String,
}
impl FakeClientAsync {
fn new(replies: impl IntoIterator<Item = &'static str>) -> Self {
Self {
fragmentizer: Fragmentizer::new(FRAGMENTIZER_MAX_MESSAGE_SIZE),
replies: replies.into_iter().map(String::from).collect(),
tag: String::new(),
}
}
}
impl ImapClientAsync for FakeClientAsync {
#[allow(clippy::manual_async_fn)]
fn run<C, T, E>(
&mut self,
mut coroutine: C,
) -> impl Future<Output = Result<T, ImapClientError>> + Send
where
C: ImapCoroutine<Yield = ImapYield, Return = Result<T, E>> + Send,
T: Send,
E: Send,
ImapClientError: From<E>,
{
async move {
let mut arg: Option<Vec<u8>> = None;
loop {
match coroutine.resume(&mut self.fragmentizer, arg.as_deref()) {
ImapCoroutineState::Complete(Ok(out)) => return Ok(out),
ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
let line = String::from_utf8(bytes).expect("utf8 command");
self.tag = line
.split_whitespace()
.next()
.expect("first whitespace-separated token")
.to_string();
arg = None;
}
ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
tokio::task::yield_now().await;
let reply = self.replies.pop_front().unwrap_or_default();
let reply = reply.replace("{tag}", &self.tag);
arg = Some(reply.into_bytes());
}
}
}
}
}
}
#[tokio::test]
async fn default_bodies_are_spawnable() {
let mut client = FakeClientAsync::new(["{tag} OK NOOP completed\r\n"]);
let handle = tokio::spawn(async move {
client.noop().await?;
Ok::<_, ImapClientError>(client)
});
handle.await.expect("task joined").expect("NOOP succeeded");
}
#[tokio::test]
async fn default_bodies_return_the_coroutine_output() {
let mut client =
FakeClientAsync::new(["* CAPABILITY IMAP4REV1 MOVE\r\n{tag} OK CAPABILITY completed\r\n"]);
let capability = client.capability().await.expect("CAPABILITY succeeded");
assert_eq!(capability.len(), 2);
}
#[tokio::test]
async fn hand_written_bodies_are_spawnable_too() {
let mut client = FakeClientAsync::new(["{tag} OK LOGIN completed\r\n"]);
let handle = tokio::spawn(async move {
client
.login("alice", "secret", Default::default())
.await
.map(|capability| capability.len())
});
let observed = handle.await.expect("task joined").expect("LOGIN succeeded");
assert_eq!(observed, 0);
}