1#![cfg(feature = "live")]
2
3#[path = "support/live.rs"]
4mod live;
5use live::*;
6use notebook::live::share::{self, Guest, Host, Sharing};
7use notebook::session::Notebook;
8use std::{
9 sync::{
10 Arc, Mutex,
11 atomic::{AtomicBool, Ordering},
12 mpsc,
13 },
14 time::Duration,
15};
16
17#[test]
18fn 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]
92fn 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]
159fn 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}