diff --git a/src/job.rs b/src/job.rs index 585c0873f..d60050c88 100644 --- a/src/job.rs +++ b/src/job.rs @@ -505,6 +505,8 @@ pub fn perform_smtp_jobs(context: &Context) { } } +pub fn perform_smtp_fetch(_context: &Context) {} + pub fn perform_smtp_idle(context: &Context) { info!(context, "SMTP-idle started...",); { diff --git a/src/location.rs b/src/location.rs index 6efef7a50..82b39795b 100644 --- a/src/location.rs +++ b/src/location.rs @@ -190,7 +190,13 @@ impl Kml { } } -// location streaming +/// Starts streaming locations to a chat. +/// +/// # Parameters +/// +/// * `context` - The [Context]. +/// * `chat_id` - The ID of the chat to send locations to. +/// * `seconds` - The duration for which to stream the location. pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) { let now = time(); if !(seconds < 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL) { diff --git a/test-data/key/private2.asc b/test-data/key/private2.asc new file mode 100644 index 000000000..d9d70c3ae --- /dev/null +++ b/test-data/key/private2.asc @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/test-data/key/public2.asc b/test-data/key/public2.asc new file mode 100644 index 000000000..443928f30 --- /dev/null +++ b/test-data/key/public2.asc @@ -0,0 +1 @@ +xsBNBF297/EBCADEgI6RhCnbfNnKqlPzHNAmCVJ65KniO0reqaR94CFxcMRcO3k5XZDAo0u1KbqobehYm39ggtoqtmattjgG/AbR+Ex90CjbT1PbIi4Efkt91deMzt4G/S/POtqnIt8nbPSohMRkTOBKI0y7DSGneZTx+uj/GuE+aWbM2ubENsW283onnUKqDUOtxqQoBT3vWocsS6iViZCjqhqqQPX4OVK0gaQDLFCm63NC5VnazSvJkmDFFhG2bN8ncZ3wVibai7r3scF+I+hCnR4ll+/Q02WdpCSW0V26DXUQiCadTri6jDAi3JAGCnsCUiXR9780yAms+idWjPAXv2l9PfvSYkITABEBAAHNETxib2JAZXhhbXBsZS5jb20+wsCJBBABCAAzAhkBBQJdvfAxAhsDBAsJCAcGFQgJCgsCAxYCARYhBMCyk5nNgMGX89zC8GVj9ZoUEAYDAAoJEGVj9ZoUEAYDWBoIAJhfY396iAauf0X+WBBTjEEJR9svmbxIIaGah3GSlYpAVXK44mLjKeJIdDzxFJ7nhfs+wEkSpU0NMnQsifdtZd+bjEN1hQxJq3WEoeyPEEp/KFOw312zt1ucysDao59oji4LLkZhKPjBz7v8e/DaWKty45Cv2t2/3+g5IWHChmyzbd0hhGpuQ6osR4iNV9xatLYWncJMUgow1YLgtAV2XBu/5B0bZA9oqHw9JX0oMWmBjHvU2ngsreUbQTcA10S2ExzfFjE9WeArTv7suVQLmcteBxLqjbFZ3UqpLraJmNejdRp0SE+OJAtiZKhq2PYPm6Wl+i2VLdScjkiqnsLjx9XOwE0EXb3v8QEIAMbfa0AldcdhCUX1ma7eZ2bA7zYLI2RbKNvBePk4Dnig/MVJBe2PUVBbo78TCxnExLZl7P7+faelKxWR5IVyy70NLglYlpL22Q/Ul3GBuUjhqCpolpkvBfqjrFa8L1Oo7g8vkrkFLPK9Ul1MVlAQ6mt/oWWObWWiO1FfHw4NiHMvGnnkRQSEg2qbmNzXvi4YIt1RhBCg1hdUJP9l1k8avRFpO9rcF3x3MM7ZuzED7zR7j0qExyguu76zYRsI/q3COy5Gw+kwl0hpN2nQSUqS0g6xZ7zxbB09ygXUS4IkZHN2tCBMgEzQh8axIQ+3ogaDC7RDESTi24+T7JXIjFY9FaEAEQEAAcLAdgQYAQgAIAUCXb3wMQIbDBYhBMCyk5nNgMGX89zC8GVj9ZoUEAYDAAoJEGVj9ZoUEAYD41wH/jIQgva+k3vmGtfYDR5tB/IdEpc6MjGJxo2NwOkBKYJfaigyK3dmZ1DY8ZfkYMfQ9s5d4cW3Lel4t7nRH5Vh5FiaIWlDuxfGVTMLNpOzlXswgHlwckrfJucVWk3/hLT/xStsSjC+SwKSC6+ejmHIqkSqbTztwVCABg63otzREV4NspEsSrO0+SUD+n2mpFFeo4ULjPXEtlJzrmoJNdByDBEODiMFUyw0voMXN13ZqFv46HVtmembBxc8tJXtHX8rvC2ODiyygI3y3HENJPYR+CBGY/v8K8sg35i7PidUEsK/V3NJRTU0WkI+NS+4b80xE5KxizQMTDNPiSuTOlb7gO4= \ No newline at end of file diff --git a/tests/location.rs b/tests/location.rs new file mode 100644 index 000000000..54905c2f8 --- /dev/null +++ b/tests/location.rs @@ -0,0 +1,476 @@ +//! Integration tests for location streaming. + +use std::collections::{HashMap, VecDeque}; +use std::mem::discriminant; +use std::path::Path; +use std::sync::{atomic, Arc, Condvar, Mutex}; +use std::thread; + +use itertools::Itertools; +use libc::uintptr_t; +use serde::Deserialize; +use tempfile; + +use deltachat::chat; +use deltachat::config::Config; +use deltachat::contact::Contact; +use deltachat::context::Context; +use deltachat::job; +use deltachat::location; +use deltachat::Event; + +/// Credentials for a test account. +/// +/// This is populated by the JSON returned from the account provider's +/// API. +#[derive(Debug, Deserialize)] +struct AccountCredentials { + email: String, + password: String, +} + +impl AccountCredentials { + /// Creates a new online account. + /// + /// Invoke the API of the account provider to create a new + /// temporary account. + fn new(provider_url: &str) -> AccountCredentials { + let (post_url, token) = provider_url.splitn(2, '#').next_tuple().unwrap(); + let mut data: HashMap<&str, u64> = HashMap::new(); + data.insert("token_create_user", token.parse().unwrap()); + let client = reqwest::Client::new(); + let mut response = client.post(post_url).json(&data).send().unwrap(); + assert!( + response.status().is_success(), + format!("Failed to create new tmpuser: {}", response.status()) + ); + response.json().unwrap() + } +} + +#[derive(Debug)] +struct EventsItem { + acc_name: String, + when: std::time::Duration, + event: Event, +} + +#[derive(Debug)] +struct EventsQueue { + name: String, + events: Mutex>, + cond: Condvar, +} + +impl EventsQueue { + fn new(name: &str) -> EventsQueue { + EventsQueue { + name: name.to_string(), + events: Mutex::new(VecDeque::new()), + cond: Condvar::new(), + } + } + + fn push(&self, evt: EventsItem) { + let mut queue = self.events.lock().unwrap(); + queue.push_back(evt); + self.cond.notify_all(); + } + + fn wait_for(&self, event: Event, data: bool) -> Result<(), ()> { + println!( + "==> [{}] Waiting for: {:?} match-data={}", + self.name, event, data + ); + let mut queue = self.events.lock().unwrap(); + let start_time = std::time::Instant::now(); + loop { + while let Some(item) = queue.pop_front() { + let hit = match data { + true => event == item.event, + false => discriminant(&event) == discriminant(&item.event), + }; + self.log_event(&item); + if hit { + println!( + "<== [{}] Found {:?} match-data={} in {:?}", + self.name, + event, + data, + start_time.elapsed() + ); + return Ok(()); + } + } + if start_time.elapsed().as_secs() > 25 { + println!( + "=!= [{}] Timed out waiting for {:?} match-data={}", + self.name, event, data + ); + return Err(()); + } + queue = self.cond.wait(queue).unwrap(); + } + } + + fn clear(&self) { + let mut queue = self.events.lock().unwrap(); + while let Some(item) = queue.pop_front() { + self.log_event(&item); + } + } + + fn log_event(&self, item: &EventsItem) { + match &item.event { + Event::Info(msg) => println!("I [{} {:?}]: {}", item.acc_name, item.when, msg), + Event::Warning(msg) => println!("W [{} {:?}]: {}", item.acc_name, item.when, msg), + Event::Error(msg) => println!("E [{} {:?}]: {}", item.acc_name, item.when, msg), + _ => println!("Evt [{} {:?}]: {:?}", item.acc_name, item.when, item.event), + } + } + + fn clear_log_events(&self) { + let mut queue = self.events.lock().unwrap(); + for item in queue.iter() { + self.log_event(item) + } + queue.retain(|item| match item.event { + Event::Info(_) | Event::Warning(_) | Event::Error(_) => false, + _ => true, + }); + } +} + +/// A Configured DeltaChat account. +#[derive(Debug)] +struct Account { + name: String, + creds: AccountCredentials, + ctx: Arc, + events: Arc, + running: Arc, + imap_handle: Option>, + mvbox_handle: Option>, + sentbox_handle: Option>, + smtp_handle: Option>, +} + +impl Account { + fn new(name: &str, dir: &Path, keys: KeyPair, start: std::time::Instant) -> Account { + // Create events queue and callback. + let events = Arc::new(EventsQueue::new(name)); + let events_cb = Arc::clone(&events); + let name_cb = name.to_string(); + let cb = move |_ctx: &Context, evt: Event| -> uintptr_t { + events_cb.push(EventsItem { + acc_name: name_cb.clone(), + when: start.elapsed(), + event: evt, + }); + 0 + }; + + // Create and configure the context. + let dbfile = dir.join(format!("{}.db", name)); + let creds = AccountCredentials::new(&Account::liveconfig_url()); + println!("Account credentials for {}: {:#?}", name, creds); + let ctx = Arc::new(Context::new(Box::new(cb), "TestClient".into(), dbfile).unwrap()); + ctx.set_config(Config::Addr, Some(&creds.email)).unwrap(); + ctx.set_config(Config::MailPw, Some(&creds.password)) + .unwrap(); + keys.save_as_self(&ctx); + deltachat::configure::configure(&ctx); + + // Start the threads. + let running = Arc::new(atomic::AtomicBool::new(true)); + let imap_handle = Self::start_imap(name, Arc::clone(&ctx), Arc::clone(&running)); + let mvbox_handle = Self::start_mvbox(name, Arc::clone(&ctx), Arc::clone(&running)); + let sentbox_handle = Self::start_sentbox(name, Arc::clone(&ctx), Arc::clone(&running)); + let smtp_handle = Self::start_smtp(name, Arc::clone(&ctx), Arc::clone(&running)); + events.clear_log_events(); + + Account { + name: name.to_string(), + creds, + ctx, + events, + running, + imap_handle: Some(imap_handle), + mvbox_handle: Some(mvbox_handle), + sentbox_handle: Some(sentbox_handle), + smtp_handle: Some(smtp_handle), + } + } + + /// Find the liveconfig URL. + /// + /// Prefers the `DCC_TMPACCOUNT_PROVIDER`, will also use the + /// `DCC_PY_LIVECONFIG` environment variable and finally fall back + /// to finding a file named `liveconfig` and starting with + /// `#:provider:https://`. + fn liveconfig_url() -> String { + if let Some(url) = std::env::var("DCC_TMPACCOUNT_PROVIDER").ok() { + return url; + } + if let Some(url) = std::env::var("DCC_PY_LIVECONFIG").ok() { + return url; + } + let mut dir = Some(Path::new(".").canonicalize().unwrap()); + loop { + let cfg_fname = match dir { + Some(path) => { + dir = path.parent().map(|p| p.to_path_buf()); + path.join("liveconfig") + } + None => break, + }; + if cfg_fname.is_file() { + let raw_data = std::fs::read(&cfg_fname).unwrap(); + let data = String::from_utf8(raw_data).unwrap(); + for line in data.lines() { + if line.starts_with("#:provider:https://") { + let (_, url) = line.split_at(11); + return url.to_string(); + } + } + panic!("No provider URL in {}", cfg_fname.display()); + } + } + panic!("Found no liveconfig"); + } + + fn start_imap( + name: &str, + ctx: Arc, + running: Arc, + ) -> thread::JoinHandle<()> { + thread::Builder::new() + .name(format!("{}-imap", name)) + .spawn(move || { + while running.load(atomic::Ordering::Relaxed) { + job::perform_imap_jobs(&ctx); + job::perform_imap_fetch(&ctx); + if !running.load(atomic::Ordering::Relaxed) { + break; + } + job::perform_imap_idle(&ctx); + } + }) + .unwrap() + } + + fn start_mvbox( + name: &str, + ctx: Arc, + running: Arc, + ) -> thread::JoinHandle<()> { + thread::Builder::new() + .name(format!("{}-mvbox", name)) + .spawn(move || { + while running.load(atomic::Ordering::Relaxed) { + job::perform_mvbox_jobs(&ctx); + job::perform_mvbox_fetch(&ctx); + if !running.load(atomic::Ordering::Relaxed) { + break; + } + job::perform_mvbox_idle(&ctx); + } + }) + .unwrap() + } + + fn start_sentbox( + name: &str, + ctx: Arc, + running: Arc, + ) -> thread::JoinHandle<()> { + thread::Builder::new() + .name(format!("{}-sentbox", name)) + .spawn(move || { + while running.load(atomic::Ordering::Relaxed) { + job::perform_sentbox_jobs(&ctx); + job::perform_sentbox_fetch(&ctx); + if !running.load(atomic::Ordering::Relaxed) { + break; + } + job::perform_sentbox_idle(&ctx); + } + }) + .unwrap() + } + + fn start_smtp( + name: &str, + ctx: Arc, + running: Arc, + ) -> thread::JoinHandle<()> { + thread::Builder::new() + .name(format!("{}-smtp", name)) + .spawn(move || { + while running.load(atomic::Ordering::Relaxed) { + job::perform_smtp_jobs(&ctx); + job::perform_smtp_fetch(&ctx); + if !running.load(atomic::Ordering::Relaxed) { + break; + } + job::perform_smtp_idle(&ctx); + } + }) + .unwrap() + } + + /// Goes through the events queue and prints all log events. + /// + /// Each processed event is removed from the queue. + fn process_log_events(&self) {} +} + +impl Drop for Account { + fn drop(&mut self) { + println!("Terminating Account {}", self.name); + self.running.store(false, atomic::Ordering::Relaxed); + job::interrupt_imap_idle(&self.ctx); + job::interrupt_mvbox_idle(&self.ctx); + self.imap_handle.take().unwrap().join().unwrap(); + self.mvbox_handle.take().unwrap().join().unwrap(); + self.events.clear(); + println!("Account {} Terminated", self.name); + } +} + +/// Helper struct to handle account key pairs. +struct KeyPair { + public: deltachat::key::Key, + private: deltachat::key::Key, +} + +impl KeyPair { + /// Create a new [KeyPair]. + /// + /// # Example + /// + /// ``` + /// let alice_keys = KeyPair::new( + /// include_str!("../test-data/key/public.asc"), + /// include_str!("../test-data/key/private.asc"), + /// ); + /// ``` + fn new(public_data: &str, private_data: &str) -> KeyPair { + let public = + deltachat::key::Key::from_base64(public_data, deltachat::constants::KeyType::Public) + .unwrap(); + let private = + deltachat::key::Key::from_base64(private_data, deltachat::constants::KeyType::Private) + .unwrap(); + KeyPair { public, private } + } + + /// Saves a key into the context as the default key of the self address. + /// + /// [Config::Addr] must already be set. + fn save_as_self(&self, ctx: &Context) { + let addr = ctx.get_config(Config::Addr).unwrap(); + let ok = deltachat::key::dc_key_save_self_keypair( + &ctx, + &self.public, + &self.private, + &addr, + true, + &ctx.sql, + ); + assert_eq!(ok, true); + } +} + +#[test] +fn test_location_streaming() { + // Create accounts + let start = std::time::Instant::now(); + let tmpdir = tempfile::tempdir().unwrap(); + let alice_keys = KeyPair::new( + include_str!("../test-data/key/public.asc"), + include_str!("../test-data/key/private.asc"), + ); + let alice = Account::new("alice", tmpdir.path(), alice_keys, start); + let bob_keys = KeyPair::new( + include_str!("../test-data/key/public2.asc"), + include_str!("../test-data/key/private2.asc"), + ); + let bob = Account::new("bob", tmpdir.path(), bob_keys, start); + alice + .events + .wait_for(Event::ConfigureProgress(1000), true) + .unwrap(); + bob.events + .wait_for(Event::ConfigureProgress(1000), true) + .unwrap(); + + // Create contacts and chats. + let contact_bob = Contact::create(&alice.ctx, "Bob", &bob.creds.email).unwrap(); + let contact_alice = Contact::create(&bob.ctx, "Alice", &bob.creds.email).unwrap(); + let alice_to_bob = deltachat::chat::create_by_contact_id(&alice.ctx, contact_bob).unwrap(); + let bob_to_alice = deltachat::chat::create_by_contact_id(&bob.ctx, contact_alice).unwrap(); + alice.events.clear(); + bob.events.clear(); + + println!("### Starting location streaming from Alice to Bob"); + assert!(!location::is_sending_locations_to_chat( + &alice.ctx, + alice_to_bob + )); + assert!(!location::is_sending_locations_to_chat( + &bob.ctx, + bob_to_alice + )); + location::send_locations_to_chat(&alice.ctx, alice_to_bob, 100); + assert!(location::is_sending_locations_to_chat( + &alice.ctx, + alice_to_bob + )); + alice + .events + .wait_for(Event::SmtpMessageSent(Default::default()), false) + .unwrap(); + assert_eq!(location::set(&alice.ctx, 1.0, 1.0, 1.0), true); + alice + .events + .wait_for(Event::LocationChanged(Default::default()), false) + .unwrap(); + assert_eq!(location::set(&alice.ctx, 1.1, 1.1, 1.0), true); + chat::send_text_msg(&alice.ctx, alice_to_bob, "ping".to_string()).unwrap(); + alice + .events + .wait_for(Event::SmtpMessageSent(Default::default()), false) + .unwrap(); + + println!("### Looking for location messages received by Bob"); + // First message is the "enabled-location-streaming" command. + bob.events + .wait_for( + Event::MsgsChanged { + chat_id: Default::default(), + msg_id: Default::default(), + }, + false, + ) + .unwrap(); + // Core emits location changed before the incoming message. Sadly + // the the ordering requirement is brittle. + bob.events + .wait_for(Event::LocationChanged(Default::default()), false) + .unwrap(); + // Next message is the "ping" one which should contain a location. + bob.events + .wait_for( + Event::MsgsChanged { + chat_id: Default::default(), + msg_id: Default::default(), + }, + false, + ) + .unwrap(); + let positions = location::get_range(&bob.ctx, bob_to_alice, contact_alice, 0, 0); + println!("pos len: {}", positions.len()); + println!("{:#?}", positions); + assert!(false, "THE END"); +}