1//! What the Live Share tests share: a relay, a host and guests on a notebook of two sections.
2#![allow(dead_code)]
3
4use notebook::{
5 EditStatus, Replica,
6 live::{
7 Hello,
8 share::{self, Guest, Host, Sharing},
9 },
10 session::{Notebook, Section},
11};
12use onestore::{
13 ExGuid,
14 op::{Edit, Op, PageOp},
15 protected::{Key, rekey},
16};
17use std::{
18 net::TcpListener,
19 path::Path,
20 sync::Arc,
21 thread,
22 time::{Duration, Instant},
23};
24
25#[path = "server.rs"]
26pub mod server;
27
28pub const PASSWORD: &str = "fixture password";
29
30/// A relay on this computer with `config`'s limits: its URL.
31pub fn relay(config: relay::server::Config) -> String {
32 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
33 let url = format!("ws://{}", listener.local_addr().unwrap());
34 thread::spawn(move || relay::server::serve(listener, config));
35 url
36}
37
38/// Another secret than `secret`, as a guess makes one.
39pub fn mistaken(secret: &str) -> String {
40 let first = if secret.starts_with('A') { 'B' } else { 'A' };
41 format!("{first}{}", &secret[1..])
42}
43
44pub fn hello(name: &str) -> Hello {
45 Hello::new(name.into(), None).unwrap()
46}
47
48pub fn until(what: &str, done: impl Fn() -> bool) {
49 let deadline = Instant::now() + Duration::from_secs(30);
50 while !done() {
51 assert!(Instant::now() < deadline, "{what}");
52 thread::sleep(Duration::from_millis(20));
53 }
54}
55
56/// A notebook folder holding `Garden.one`, a page reading "Original text", and a protected
57/// `Sealed.one` reading "Sealed text".
58pub fn notebook(root: &Path) -> std::path::PathBuf {
59 let folder = root.join("Garden");
60 std::fs::create_dir_all(&folder).unwrap();
61 std::fs::write(
62 folder.join("Garden.one"),
63 onestore::create_section("Garden.one", "Original text", "Fixture").unwrap(),
64 )
65 .unwrap();
66 let plain = onestore::create_section("Sealed.one", "Sealed text", "Fixture").unwrap();
67 let key = Key::new(PASSWORD).unwrap();
68 std::fs::write(
69 folder.join("Sealed.one"),
70 rekey(&plain, None, Some(&key)).unwrap(),
71 )
72 .unwrap();
73 folder
74}
75
76pub fn host(folder: &Path, cache: &Path, sharing: &Sharing, url: &str) -> Host {
77 let storage = Notebook::open(folder, cache).unwrap().into_storage();
78 Host::start(
79 storage,
80 hello("Ada"),
81 sharing.clone(),
82 "Garden",
83 None,
84 Some(url),
85 || {},
86 |_| Ok(()),
87 )
88 .unwrap()
89}
90
91/// The host's code once the relay has numbered it.
92pub fn code(host: &Host) -> String {
93 until("the code was never numbered", || {
94 host.code().is_some_and(|code| share::code(&code).is_some())
95 });
96 host.code().unwrap()
97}
98
99/// `name` joins with `code` and opens the notebook in `cache`.
100pub fn guest(name: &str, code: &str, url: &str, cache: &Path) -> (Arc<Guest>, Notebook) {
101 let welcome = share::join(hello(name), code, "", None, Some(url)).unwrap();
102 assert_eq!(
103 (welcome.notebook.as_str(), welcome.host.as_str()),
104 ("Garden", "Ada")
105 );
106 let guest = Guest::start(
107 hello(name),
108 welcome.share,
109 welcome.secret,
110 None,
111 Some(url),
112 || {},
113 )
114 .unwrap();
115 until("the host was never met", || guest.host().is_some());
116 let notebook = Notebook::open_hosted(Arc::clone(&guest), cache).unwrap();
117 (guest, notebook)
118}
119
120pub fn open(notebook: &Notebook, guest: &Arc<Guest>, path: &str, key: Option<&Key>) -> Section {
121 let replica = notebook.replica_path(path).unwrap();
122 std::fs::create_dir_all(replica.parent().unwrap()).unwrap();
123 let replica = Replica::open_or_create(&replica, key, || notebook.read_section(path)).unwrap();
124 Section::resume_hosted(path.into(), replica, Arc::clone(guest), || {}).unwrap()
125}
126
127pub fn replace(section: &Section, image: &[u8], range: std::ops::Range<u32>, with: &str) -> u64 {
128 let (space, text, _) = server::text(image);
129 replaced(section, space, text, range, with)
130}
131
132pub fn replaced(
133 section: &Section,
134 space: ExGuid,
135 text: ExGuid,
136 range: std::ops::Range<u32>,
137 with: &str,
138) -> u64 {
139 let op = PageOp::Text {
140 text,
141 range,
142 with: with.into(),
143 };
144 let edit = Edit {
145 at: 134_000_000_000_000_000,
146 ops: vec![Op::Page { space, op }],
147 };
148 section.replica().apply("Guest", edit).unwrap()
149}
150
151pub fn published(section: &Section, id: u64) {
152 until("the edit was never published", || {
153 matches!(
154 section.status(id).unwrap(),
155 Some(EditStatus::Published { .. })
156 )
157 });
158}