Skip to main content

max / goingson

5.0 KB · 142 lines History Blame Raw
1 //! The Sync section, driven through the router.
2 //!
3 //! The section exists because somebody measured `commands/sync.rs` per command
4 //! rather than per file, so the tests worth reading are the ones that pin what
5 //! is here and what is not. A later pass that "finishes" this section by adding
6 //! Sync Now would be undoing a measurement, not filling a gap.
7
8 use std::sync::Arc;
9
10 use quasi_http::Serves as _;
11 use quasi_router::{Outcome, Params, Request};
12
13 use crate::quasi::router;
14 use crate::state::AppState;
15
16 async fn state() -> Arc<AppState> {
17 let (state, _) = crate::test_utils::setup_test_state().await;
18 state
19 }
20
21 fn html(state: &AppState, path: &str) -> String {
22 let response = router()
23 .handle(state, Request::get(path).carrying(Params::new()))
24 .expect("the route answers");
25 match &response.outcome {
26 Outcome::Screen(screen) => quasi_webview::Webview::new().screen(screen),
27 Outcome::Fragment { node, .. } => quasi_webview::Webview::new().fragment(node),
28 other => panic!("expected content, got {other:?}"),
29 }
30 }
31
32 #[tokio::test]
33 async fn sync_is_a_section_of_settings() {
34 let state = state().await;
35 let markup = html(&state, "/settings/sync");
36 assert!(markup.contains("Cloud Sync"), "{markup}");
37 assert!(markup.contains("/settings/sync"), "the sidebar offers it");
38 }
39
40 #[tokio::test]
41 async fn a_device_with_no_sync_says_so_rather_than_drawing_an_empty_form() {
42 // A test state has no sync client. Drawing dashes and disabled switches
43 // would be a section pretending to be about something.
44 let state = state().await;
45 assert!(
46 state.read_recovering().is_none(),
47 "the fixture has no client"
48 );
49
50 let markup = html(&state, "/settings/sync");
51 assert!(markup.contains("not set up"), "{markup}");
52 assert!(!markup.contains("Minutes between syncs"), "{markup}");
53 assert!(!markup.contains("Disconnect"), "{markup}");
54 }
55
56 #[tokio::test]
57 async fn the_settings_are_written_even_with_no_client() {
58 // The switch and the interval are `sync_state` rows the scheduler reads.
59 // They are not the client's, so they are writable whether or not sync is
60 // set up, and this is the half that proves the section is local.
61 let state = state().await;
62
63 let response = router()
64 .handle(
65 &state,
66 Request::post("/settings/sync/interval")
67 .sending(Params::new().with(super::INTERVAL, "30")),
68 )
69 .expect("the route answers");
70 assert!(matches!(response.outcome, Outcome::Fragment { .. }));
71
72 let written =
73 crate::syncstore::sync_state::get_sync_states_batch(&state.db, &[super::INTERVAL])
74 .expect("read back");
75 assert_eq!(written.get(super::INTERVAL).map(String::as_str), Some("30"));
76 }
77
78 #[tokio::test]
79 async fn a_silly_interval_is_clamped_rather_than_refused() {
80 // What every other numeric control on this screen does.
81 let state = state().await;
82 router()
83 .handle(
84 &state,
85 Request::post("/settings/sync/interval")
86 .sending(Params::new().with(super::INTERVAL, "999999")),
87 )
88 .expect("the route answers");
89
90 let written =
91 crate::syncstore::sync_state::get_sync_states_batch(&state.db, &[super::INTERVAL])
92 .expect("read back");
93 assert_eq!(
94 written.get(super::INTERVAL).map(String::as_str),
95 Some("1440")
96 );
97 }
98
99 #[tokio::test]
100 async fn turning_automatic_syncing_off_writes_the_row_the_scheduler_reads() {
101 let state = state().await;
102 router()
103 .handle(
104 &state,
105 Request::post("/settings/sync/auto")
106 .sending(Params::new().with(super::AUTO_SYNC, "disabled")),
107 )
108 .expect("the route answers");
109
110 let written =
111 crate::syncstore::sync_state::get_sync_states_batch(&state.db, &[super::AUTO_SYNC])
112 .expect("read back");
113 assert_eq!(written.get(super::AUTO_SYNC).map(String::as_str), Some("0"));
114
115 // Absent means on, so turning it back on has to write the row rather than
116 // clear it: the scheduler reads `is_none_or(|v| v == "1")`.
117 router()
118 .handle(
119 &state,
120 Request::post("/settings/sync/auto")
121 .sending(Params::new().with(super::AUTO_SYNC, "enabled")),
122 )
123 .expect("the route answers");
124 let written =
125 crate::syncstore::sync_state::get_sync_states_batch(&state.db, &[super::AUTO_SYNC])
126 .expect("read back");
127 assert_eq!(written.get(super::AUTO_SYNC).map(String::as_str), Some("1"));
128 }
129
130 #[tokio::test]
131 async fn the_controls_that_need_a_server_are_absent() {
132 // Pinned so they read as a measurement rather than an unfinished section.
133 // Each of these is a conversation with a server, which a synchronous route
134 // handler cannot have; see the module header and quasicoherent 82273265.
135 let state = state().await;
136 let markup = html(&state, "/settings/sync");
137
138 for absent in ["Sync Now", "Set up encryption", "Subscribe", "Connect"] {
139 assert!(!markup.contains(absent), "should not offer: {absent}");
140 }
141 }
142