1use crate::types::{NetworkDiagnostics, SpeedTestResult};
14
15pub type EventSender = tokio::sync::mpsc::UnboundedSender<TestEvent>;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Phase {
21 Locating,
23 BuildingServers,
25 SelectingServers,
27 Latency,
29 Download,
31 Upload,
33 Jitter,
35}
36
37impl Phase {
38 pub fn title(self) -> &'static str {
40 match self {
41 Phase::Locating => "Detecting Location",
42 Phase::BuildingServers => "Building Server Pool",
43 Phase::SelectingServers => "Selecting Best Servers",
44 Phase::Latency => "Testing Latency",
45 Phase::Download => "Testing Download Speed",
46 Phase::Upload => "Testing Upload Speed",
47 Phase::Jitter => "Measuring Jitter & Packet Loss",
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct SelectedServer {
55 pub name: String,
56 pub location: String,
57 pub latency_ms: f64,
58 pub distance_km: f64,
59}
60
61#[derive(Debug, Clone)]
63pub enum TestEvent {
64 Status(String),
66 LocationDetected {
68 city: String,
69 country: String,
70 isp: Option<String>,
71 source: String,
72 },
73 ServerPoolBuilt { count: usize },
75 NearbyServersFound { count: usize },
77 ServersSelected { servers: Vec<SelectedServer> },
79 PrimarySelected {
81 name: String,
82 location: String,
83 distance_km: f64,
84 },
85 PhaseStarted(Phase),
87 DownloadSample {
89 mbps: f64,
90 peak_mbps: f64,
91 elapsed_secs: f64,
92 },
93 UploadSample {
95 mbps: f64,
96 peak_mbps: f64,
97 elapsed_secs: f64,
98 },
99 DownloadComplete { mbps: f64 },
101 UploadComplete { mbps: f64 },
103 LatencyProgress { avg_ms: f64 },
105 LatencyComplete { avg_ms: f64 },
107 JitterComplete {
109 jitter_ms: f64,
110 packet_loss_percent: f64,
111 },
112 DiagnosticsComplete(Box<NetworkDiagnostics>),
114 Completed(Box<SpeedTestResult>),
116}
117
118#[inline]
121pub fn emit(tx: &Option<EventSender>, event: TestEvent) {
122 if let Some(tx) = tx {
123 let _ = tx.send(event);
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn phase_titles_are_present() {
133 assert_eq!(Phase::Download.title(), "Testing Download Speed");
134 assert_eq!(Phase::Upload.title(), "Testing Upload Speed");
135 assert_eq!(Phase::Latency.title(), "Testing Latency");
136 }
137
138 #[test]
139 fn emit_is_a_noop_without_a_sender() {
140 emit(&None, TestEvent::Status("hello".into()));
142 }
143
144 #[test]
145 fn emit_delivers_to_receiver() {
146 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
147 emit(&Some(tx), TestEvent::ServerPoolBuilt { count: 7 });
148 match rx.try_recv() {
149 Ok(TestEvent::ServerPoolBuilt { count }) => assert_eq!(count, 7),
150 other => panic!("unexpected event: {other:?}"),
151 }
152 }
153}