| 1 | #![cfg(feature = "live")] |
| 2 | |
| 3 | #[path = "support/live.rs"] |
| 4 | mod live; |
| 5 | use live::*; |
| 6 | use notebook::live::share::{self, Guest, Host, Sharing}; |
| 7 | use notebook::session::Notebook; |
| 8 | use std::{ |
| 9 | sync::{ |
| 10 | Arc, Mutex, |
| 11 | atomic::{AtomicBool, Ordering}, |
| 12 | mpsc, |
| 13 | }, |
| 14 | time::Duration, |
| 15 | }; |
| 16 | |
| 17 | #[test] |
| 18 | fn approval_keeps_credentials_before_welcome_and_a_decline_grants_nothing() { |
| 19 | let directory = tempfile::tempdir().unwrap(); |
| 20 | let folder = notebook(directory.path()); |
| 21 | let url = relay(Default::default()); |
| 22 | let saved = Arc::new(Mutex::new(None)); |
| 23 | let fail = Arc::new(AtomicBool::new(false)); |
| 24 | let (kept, failing) = (Arc::clone(&saved), Arc::clone(&fail)); |
| 25 | let mut sharing = Sharing::new("").unwrap(); |
| 26 | sharing.approve = true; |
| 27 | let host = Host::start( |
| 28 | Notebook::open(&folder, directory.path().join("host")) |
| 29 | .unwrap() |
| 30 | .into_storage(), |
| 31 | hello("Ada"), |
| 32 | sharing, |
| 33 | "Garden", |
| 34 | None, |
| 35 | Some(&url), |
| 36 | || {}, |
| 37 | move |sharing| { |
| 38 | if failing.load(Ordering::Acquire) { |
| 39 | return Err(std::io::ErrorKind::PermissionDenied.into()); |
| 40 | } |
| 41 | *kept.lock().unwrap() = Some(serde_json::to_vec(sharing).unwrap()); |
| 42 | Ok(()) |
| 43 | }, |
| 44 | ) |
| 45 | .unwrap(); |
| 46 | let code = self::code(&host); |
| 47 | let (reply, result) = mpsc::channel(); |
| 48 | let joining = url.clone(); |
| 49 | std::thread::spawn(move || { |
| 50 | let _ = reply.send(share::join(hello("Grace"), &code, "", None, Some(&joining))); |
| 51 | }); |
| 52 | until("the request never arrived", || host.requests().len() == 1); |
| 53 | assert!(result.try_recv().is_err()); |
| 54 | assert!(host.devices().is_empty()); |
| 55 | let peer = host.requests()[0].peer; |
| 56 | fail.store(true, Ordering::Release); |
| 57 | assert!(host.allow(&peer).is_err()); |
| 58 | assert!(host.devices().is_empty()); |
| 59 | assert_eq!(host.requests().len(), 1); |
| 60 | fail.store(false, Ordering::Release); |
| 61 | host.allow(&peer).unwrap(); |
| 62 | let welcome = result |
| 63 | .recv_timeout(Duration::from_secs(5)) |
| 64 | .unwrap() |
| 65 | .unwrap(); |
| 66 | let persisted: Sharing = |
| 67 | serde_json::from_slice(saved.lock().unwrap().as_ref().unwrap()).unwrap(); |
| 68 | assert_eq!(persisted.members[0].secret, welcome.secret); |
| 69 | assert_ne!(welcome.secret, welcome.room); |
| 70 | fail.store(true, Ordering::Release); |
| 71 | assert!(host.remove(&welcome.secret).is_err()); |
| 72 | assert_eq!(host.sharing(), persisted); |
| 73 | fail.store(false, Ordering::Release); |
| 74 | |
| 75 | let code = self::code(&host); |
| 76 | let (reply, result) = mpsc::channel(); |
| 77 | std::thread::spawn(move || { |
| 78 | let _ = reply.send(share::join(hello("Alan"), &code, "", None, Some(&url))); |
| 79 | }); |
| 80 | until("the second request never arrived", || { |
| 81 | host.requests().len() == 1 |
| 82 | }); |
| 83 | host.decline(&host.requests()[0].peer); |
| 84 | assert_eq!( |
| 85 | result.recv_timeout(Duration::from_secs(5)).unwrap(), |
| 86 | Err(share::Refusal::Declined) |
| 87 | ); |
| 88 | assert_eq!(host.devices().len(), 1); |
| 89 | } |
| 90 | |
| 91 | #[test] |
| 92 | fn removal_retires_one_credential_and_other_devices_reconnect_after_a_restart() { |
| 93 | let directory = tempfile::tempdir().unwrap(); |
| 94 | let folder = notebook(directory.path()); |
| 95 | let url = relay(Default::default()); |
| 96 | let sharing = Sharing::new("").unwrap(); |
| 97 | let host_cache = directory.path().join("host"); |
| 98 | let host = host(&folder, &host_cache, &sharing, &url); |
| 99 | let original_code = code(&host); |
| 100 | let (alice, _) = guest( |
| 101 | "Alice", |
| 102 | &original_code, |
| 103 | &url, |
| 104 | &directory.path().join("alice"), |
| 105 | ); |
| 106 | let (bob, _) = guest("Bob", &original_code, &url, &directory.path().join("bob")); |
| 107 | let before = host.sharing(); |
| 108 | let alice_key = before |
| 109 | .members |
| 110 | .iter() |
| 111 | .find(|device| device.name == "Alice") |
| 112 | .unwrap() |
| 113 | .secret; |
| 114 | let bob_key = before |
| 115 | .members |
| 116 | .iter() |
| 117 | .find(|device| device.name == "Bob") |
| 118 | .unwrap() |
| 119 | .secret; |
| 120 | host.remove(&bob_key).unwrap(); |
| 121 | until("Bob was not removed", || { |
| 122 | bob.ended() == Some(share::Ended::Removed) && bob.host().is_none() |
| 123 | }); |
| 124 | until("presence did not move to the new room", || { |
| 125 | host.guests().len() == 1 |
| 126 | }); |
| 127 | let current = host.sharing(); |
| 128 | assert_ne!(before.secret, current.secret); |
| 129 | assert_eq!(current.members.len(), 1); |
| 130 | assert_ne!(original_code, code(&host)); |
| 131 | assert!(alice.host().is_some()); |
| 132 | assert_eq!(alice.ended(), None); |
| 133 | let live = Notebook::open_hosted(Arc::clone(&alice), directory.path().join("alice")).unwrap(); |
| 134 | assert_eq!(live.catalog().sections.len(), 2); |
| 135 | let restarted: Sharing = |
| 136 | serde_json::from_slice(&serde_json::to_vec(&current).unwrap()).unwrap(); |
| 137 | drop(host); |
| 138 | until("Alice's connection did not close", || { |
| 139 | alice.host().is_none() |
| 140 | }); |
| 141 | let host = self::host(&folder, &host_cache, &restarted, &url); |
| 142 | until("Alice did not reconnect", || alice.host().is_some()); |
| 143 | assert_eq!(host.devices()[0].0.secret, alice_key); |
| 144 | let forged = Guest::start( |
| 145 | hello("Alice"), |
| 146 | current.share, |
| 147 | bob_key, |
| 148 | None, |
| 149 | Some(&url), |
| 150 | || {}, |
| 151 | ) |
| 152 | .unwrap(); |
| 153 | std::thread::sleep(Duration::from_millis(500)); |
| 154 | assert!(forged.host().is_none()); |
| 155 | assert!(Notebook::open_hosted(forged, directory.path().join("forged")).is_err()); |
| 156 | } |
| 157 | |
| 158 | #[test] |
| 159 | fn cancelling_a_join_retires_its_pending_request() { |
| 160 | let directory = tempfile::tempdir().unwrap(); |
| 161 | let folder = notebook(directory.path()); |
| 162 | let url = relay(Default::default()); |
| 163 | let mut sharing = Sharing::new("").unwrap(); |
| 164 | sharing.approve = true; |
| 165 | let host = host(&folder, &directory.path().join("host"), &sharing, &url); |
| 166 | let code = code(&host); |
| 167 | let alive = Arc::new(AtomicBool::new(true)); |
| 168 | let continuing = Arc::clone(&alive); |
| 169 | let (reply, result) = mpsc::channel(); |
| 170 | std::thread::spawn(move || { |
| 171 | let _ = reply.send(share::join_while( |
| 172 | hello("Grace"), |
| 173 | &code, |
| 174 | "", |
| 175 | None, |
| 176 | Some(&url), |
| 177 | |_| continuing.load(Ordering::Acquire), |
| 178 | )); |
| 179 | }); |
| 180 | until("the request never arrived", || !host.requests().is_empty()); |
| 181 | alive.store(false, Ordering::Release); |
| 182 | assert_eq!( |
| 183 | result.recv_timeout(Duration::from_secs(5)).unwrap(), |
| 184 | Err(share::Refusal::Cancelled) |
| 185 | ); |
| 186 | until("the cancelled request stayed", || { |
| 187 | host.requests().is_empty() |
| 188 | }); |
| 189 | assert!(host.devices().is_empty()); |
| 190 | } |