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 until("the initial presence room never met both devices", || {
108 host.guests().len() == 2
109 });
110 let before = host.sharing();
111 let alice_key = before
112 .members
113 .iter()
114 .find(|device| device.name == "Alice")
115 .unwrap()
116 .secret;
117 let bob_key = before
118 .members
119 .iter()
120 .find(|device| device.name == "Bob")
121 .unwrap()
122 .secret;
123 host.remove(&bob_key).unwrap();
124 until("Bob was not removed", || {
125 bob.ended() == Some(share::Ended::Removed) && bob.host().is_none()
126 });
127 until("presence did not move to the new room", || {
128 let guests = host.guests();
129 guests.len() == 1 && guests[0].hello.name == "Alice"
130 });
131 let current = host.sharing();
132 assert_ne!(before.secret, current.secret);
133 assert_eq!(current.members.len(), 1);
134 assert_ne!(original_code, code(&host));
135 assert!(alice.host().is_some());
136 assert_eq!(alice.ended(), None);
137 let live = Notebook::open_hosted(Arc::clone(&alice), directory.path().join("alice")).unwrap();
138 assert_eq!(live.catalog().sections.len(), 2);
139 let restarted: Sharing =
140 serde_json::from_slice(&serde_json::to_vec(&current).unwrap()).unwrap();
141 drop(host);
142 until("Alice's connection did not close", || {
143 alice.host().is_none()
144 });
145 let host = self::host(&folder, &host_cache, &restarted, &url);
146 until("Alice did not reconnect", || alice.host().is_some());
147 assert_eq!(host.devices()[0].0.secret, alice_key);
148 let forged = Guest::start(
149 hello("Alice"),
150 current.share,
151 bob_key,
152 None,
153 Some(&url),
154 || {},
155 )
156 .unwrap();
157 std::thread::sleep(Duration::from_millis(500));
158 assert!(forged.host().is_none());
159 assert!(Notebook::open_hosted(forged, directory.path().join("forged")).is_err());
160}
161
162#[test]
163fn cancelling_a_join_retires_its_pending_request() {
164 let directory = tempfile::tempdir().unwrap();
165 let folder = notebook(directory.path());
166 let url = relay(Default::default());
167 let mut sharing = Sharing::new("").unwrap();
168 sharing.approve = true;
169 let host = host(&folder, &directory.path().join("host"), &sharing, &url);
170 let code = code(&host);
171 let alive = Arc::new(AtomicBool::new(true));
172 let continuing = Arc::clone(&alive);
173 let (reply, result) = mpsc::channel();
174 std::thread::spawn(move || {
175 let _ = reply.send(share::join_while(
176 hello("Grace"),
177 &code,
178 "",
179 None,
180 Some(&url),
181 |_| continuing.load(Ordering::Acquire),
182 ));
183 });
184 until("the request never arrived", || !host.requests().is_empty());
185 alive.store(false, Ordering::Release);
186 assert_eq!(
187 result.recv_timeout(Duration::from_secs(5)).unwrap(),
188 Err(share::Refusal::Cancelled)
189 );
190 until("the cancelled request stayed", || {
191 host.requests().is_empty()
192 });
193 assert!(host.devices().is_empty());
194}