cli_speedtest/
cooldown.rs1use std::path::PathBuf;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6pub const DEFAULT_COOLDOWN_SECS: u64 = 300; pub fn last_run_path() -> Option<PathBuf> {
12 if let Ok(p) = std::env::var("SPEEDTEST_MOCK_DATA_DIR") {
13 return Some(PathBuf::from(p).join("speedtest").join("last_run"));
14 }
15 dirs::data_local_dir().map(|d| d.join("speedtest").join("last_run"))
16}
17
18pub fn cooldown_remaining(cooldown_secs: u64) -> Option<u64> {
21 let path = last_run_path()?;
22 let contents = std::fs::read_to_string(&path).ok()?;
23 let last_run_ts: u64 = contents.trim().parse().ok()?;
24 let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
25 let elapsed = now.saturating_sub(last_run_ts);
26 if elapsed < cooldown_secs {
27 Some(cooldown_secs - elapsed)
28 } else {
29 None
30 }
31}
32
33pub fn record_successful_run() -> anyhow::Result<()> {
38 let path =
39 last_run_path().ok_or_else(|| anyhow::anyhow!("Could not determine data directory"))?;
40 if let Some(parent) = path.parent() {
41 std::fs::create_dir_all(parent)?;
42 }
43 let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
44 std::fs::write(&path, now.to_string())?;
45 Ok(())
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use std::fs;
52 use std::sync::Mutex;
53 use tempfile::TempDir;
54
55 static ENV_LOCK: Mutex<()> = Mutex::new(());
56
57 fn setup_test_env() -> TempDir {
58 let temp = TempDir::new().expect("Failed to create temp dir");
59 unsafe {
60 std::env::set_var("SPEEDTEST_MOCK_DATA_DIR", temp.path());
61 }
62 temp
63 }
64
65 #[test]
66 fn cooldown_none_when_no_file() {
67 let _guard = ENV_LOCK.lock().unwrap();
68 let _temp = setup_test_env();
69 assert_eq!(cooldown_remaining(DEFAULT_COOLDOWN_SECS), None);
70 }
71
72 #[test]
73 fn cooldown_none_when_elapsed() {
74 let _guard = ENV_LOCK.lock().unwrap();
75 let _temp = setup_test_env();
76 let path = last_run_path().unwrap();
77 fs::create_dir_all(path.parent().unwrap()).unwrap();
78
79 let old_time = SystemTime::now()
80 .duration_since(UNIX_EPOCH)
81 .unwrap()
82 .as_secs()
83 - 1000;
84 fs::write(&path, old_time.to_string()).unwrap();
85
86 assert_eq!(cooldown_remaining(DEFAULT_COOLDOWN_SECS), None);
87 }
88
89 #[test]
90 fn cooldown_some_when_active() {
91 let _guard = ENV_LOCK.lock().unwrap();
92 let _temp = setup_test_env();
93 let path = last_run_path().unwrap();
94 fs::create_dir_all(path.parent().unwrap()).unwrap();
95
96 let recent_time = SystemTime::now()
97 .duration_since(UNIX_EPOCH)
98 .unwrap()
99 .as_secs()
100 - 100;
101 fs::write(&path, recent_time.to_string()).unwrap();
102
103 let remaining = cooldown_remaining(DEFAULT_COOLDOWN_SECS);
104 assert!(remaining.is_some());
105 assert!(remaining.unwrap() <= 200); }
107
108 #[test]
109 fn record_run_creates_file() {
110 let _guard = ENV_LOCK.lock().unwrap();
111 let _temp = setup_test_env();
112
113 let path = last_run_path().unwrap();
115 assert!(!path.exists());
116
117 record_successful_run().expect("Should record successfully");
118
119 assert!(path.exists());
120 let content = fs::read_to_string(&path).unwrap();
121 assert!(content.trim().parse::<u64>().is_ok());
122 }
123
124 #[test]
125 fn record_run_creates_missing_dirs() {
126 let _guard = ENV_LOCK.lock().unwrap();
127 let temp = setup_test_env();
128
129 let speedtest_dir = temp.path().join("speedtest");
131 if speedtest_dir.exists() {
132 fs::remove_dir_all(&speedtest_dir).unwrap();
133 }
134
135 record_successful_run().expect("Should record successfully with missing parent dir");
136
137 let path = last_run_path().unwrap();
138 assert!(path.exists());
139 }
140}