1use std::collections::HashMap;
2use std::collections::HashSet;
3use std::sync::Arc;
4use std::sync::LazyLock;
5use std::sync::Mutex;
6
7use log::info;
8use log::warn;
9use serde::{
10 Deserialize,
11 Serialize,
12};
13use sqlx::{
14 Row,
15 SqlitePool,
16};
17use tokio::sync::RwLock;
18
19use crate::models::settings_model::AppSettings;
20use crate::utils::db::get_db_pool;
21use crate::utils::db_mode::{
22 DatabaseManager,
23 DatabaseMode,
24};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Setting {
28 pub key: String,
29 pub value: String,
30 pub updated_at: Option<String>,
31}
32
33pub struct SettingsManager {
34 cache: Arc<RwLock<HashMap<String, String>>>,
35}
36
37impl SettingsManager {
38 pub fn new() -> Self {
39 Self {
40 cache: Arc::new(RwLock::new(HashMap::new())),
41 }
42 }
43
44 pub async fn load_settings(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
45 let pool = get_db_pool().await?;
46 let settings = load_all_settings(&pool).await?;
47
48 let mut cache = self.cache.write().await;
49 cache.clear();
50 for setting in settings {
51 cache.insert(setting.key, setting.value);
52 }
53 info!("Loaded {} settings from database", cache.len());
54 Ok(())
55 }
56
57 pub async fn get_setting(&self, key: &str) -> Option<String> {
58 let cache = self.cache.read().await;
59 cache.get(key).cloned()
60 }
61
62 pub async fn set_setting(
63 &self, key: &str, value: &str,
64 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
65 let pool = get_db_pool().await?;
66 upsert_setting(&pool, key, value).await?;
67
68 let mut cache = self.cache.write().await;
69 cache.insert(key.to_string(), value.to_string());
70 info!("Set setting: {key} = {value}");
71 Ok(())
72 }
73
74 pub async fn get_disconnect_timeout(&self) -> Option<u32> {
75 if let Some(value) = self.get_setting("disconnect_timeout_minutes").await {
76 value.parse::<u32>().ok()
77 } else {
78 Some(0) }
80 }
81
82 pub async fn set_disconnect_timeout(
83 &self, minutes: u32,
84 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
85 self.set_setting("disconnect_timeout_minutes", &minutes.to_string())
86 .await
87 }
88
89 pub async fn get_network_monitor(&self) -> bool {
90 if let Some(value) = self.get_setting("network_monitor").await {
91 value.parse::<bool>().unwrap_or(true)
92 } else {
93 true }
95 }
96
97 pub async fn set_network_monitor(
98 &self, enabled: bool,
99 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
100 self.set_setting("network_monitor", &enabled.to_string())
101 .await
102 }
103
104 pub async fn get_http_logs_default_enabled(&self) -> bool {
105 if let Some(value) = self.get_setting("http_logs_default_enabled").await {
106 value.parse::<bool>().unwrap_or(false)
107 } else {
108 false }
110 }
111
112 pub async fn set_http_logs_default_enabled(
113 &self, enabled: bool,
114 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
115 self.set_setting("http_logs_default_enabled", &enabled.to_string())
116 .await
117 }
118
119 pub async fn get_http_logs_max_file_size(&self) -> u64 {
120 if let Some(value) = self.get_setting("http_logs_max_file_size").await {
121 value.parse::<u64>().unwrap_or(10 * 1024 * 1024)
122 } else {
123 10 * 1024 * 1024 }
125 }
126
127 pub async fn set_http_logs_max_file_size(
128 &self, size: u64,
129 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
130 self.set_setting("http_logs_max_file_size", &size.to_string())
131 .await
132 }
133
134 pub async fn get_http_logs_retention_days(&self) -> u64 {
135 if let Some(value) = self.get_setting("http_logs_retention_days").await {
136 value.parse::<u64>().unwrap_or(7)
137 } else {
138 7 }
140 }
141
142 pub async fn set_http_logs_retention_days(
143 &self, days: u64,
144 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
145 self.set_setting("http_logs_retention_days", &days.to_string())
146 .await
147 }
148
149 pub async fn get_auto_update_enabled(&self) -> bool {
150 if let Some(value) = self.get_setting("auto_update_enabled").await {
151 value.parse::<bool>().unwrap_or(true)
152 } else {
153 true
154 }
155 }
156
157 pub async fn set_auto_update_enabled(
158 &self, enabled: bool,
159 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
160 self.set_setting("auto_update_enabled", &enabled.to_string())
161 .await
162 }
163
164 pub async fn get_last_update_check(&self) -> Option<i64> {
165 if let Some(value) = self.get_setting("last_update_check").await {
166 value.parse::<i64>().ok()
167 } else {
168 None
169 }
170 }
171
172 pub async fn set_last_update_check(
173 &self, timestamp: i64,
174 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
175 self.set_setting("last_update_check", ×tamp.to_string())
176 .await
177 }
178
179 pub async fn get_all_settings(&self) -> HashMap<String, String> {
180 let cache = self.cache.read().await;
181 cache.clone()
182 }
183
184 pub async fn get_app_settings(&self) -> AppSettings {
185 let settings = self.get_all_settings().await;
186 AppSettings::from_settings_manager(&settings)
187 }
188
189 pub async fn set_app_settings(
190 &self, app_settings: &AppSettings,
191 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
192 let settings_map = app_settings.to_settings_map();
193
194 for (key, value) in settings_map.iter() {
195 self.set_setting(key, value).await?;
196 }
197
198 Ok(())
199 }
200
201 pub async fn get_ssl_enabled(&self) -> bool {
202 if let Some(value) = self.get_setting("ssl_enabled").await {
203 value.parse::<bool>().unwrap_or(false)
204 } else {
205 false
206 }
207 }
208
209 pub async fn set_ssl_enabled(
210 &self, enabled: bool,
211 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
212 self.set_setting("ssl_enabled", &enabled.to_string()).await
213 }
214
215 pub async fn get_ssl_cert_validity_days(&self) -> u16 {
216 if let Some(value) = self.get_setting("ssl_cert_validity_days").await {
217 value.parse::<u16>().unwrap_or(365)
218 } else {
219 365
220 }
221 }
222
223 pub async fn set_ssl_cert_validity_days(
224 &self, days: u16,
225 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
226 self.set_setting("ssl_cert_validity_days", &days.to_string())
227 .await
228 }
229
230 pub async fn get_ssl_auto_regenerate(&self) -> bool {
231 if let Some(value) = self.get_setting("ssl_auto_regenerate").await {
232 value.parse::<bool>().unwrap_or(true)
233 } else {
234 true
235 }
236 }
237
238 pub async fn set_ssl_auto_regenerate(
239 &self, enabled: bool,
240 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
241 self.set_setting("ssl_auto_regenerate", &enabled.to_string())
242 .await
243 }
244
245 pub async fn get_ssl_ca_auto_install(&self) -> bool {
246 if let Some(value) = self.get_setting("ssl_ca_auto_install").await {
247 value.parse::<bool>().unwrap_or(false)
248 } else {
249 false
250 }
251 }
252
253 pub async fn set_ssl_ca_auto_install(
254 &self, enabled: bool,
255 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
256 self.set_setting("ssl_ca_auto_install", &enabled.to_string())
257 .await
258 }
259
260 pub async fn get_global_shortcut(&self) -> String {
261 if let Some(value) = self.get_setting("global_shortcut").await {
262 value
263 } else {
264 "Ctrl+Shift+F1".to_string()
265 }
266 }
267
268 pub async fn set_global_shortcut(
269 &self, shortcut: &str,
270 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
271 self.set_setting("global_shortcut", shortcut).await
272 }
273
274 pub async fn get_mcp_server_enabled(&self) -> bool {
275 if let Some(value) = self.get_setting("mcp_server_enabled").await {
276 value.parse::<bool>().unwrap_or(false)
277 } else {
278 false
279 }
280 }
281
282 pub async fn set_mcp_server_enabled(
283 &self, enabled: bool,
284 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
285 self.set_setting("mcp_server_enabled", &enabled.to_string())
286 .await
287 }
288
289 pub async fn get_mcp_server_port(&self) -> u16 {
290 if let Some(value) = self.get_setting("mcp_server_port").await {
291 value.parse::<u16>().unwrap_or(3000)
292 } else {
293 3000
294 }
295 }
296
297 pub async fn set_mcp_server_port(
298 &self, port: u16,
299 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
300 self.set_setting("mcp_server_port", &port.to_string()).await
301 }
302}
303
304impl Default for SettingsManager {
305 fn default() -> Self {
306 Self::new()
307 }
308}
309
310async fn load_all_settings(pool: &SqlitePool) -> Result<Vec<Setting>, sqlx::Error> {
311 let mut conn = pool.acquire().await?;
312 let rows = sqlx::query("SELECT key, value, updated_at FROM settings ORDER BY key")
313 .fetch_all(&mut *conn)
314 .await?;
315
316 let settings = rows
317 .into_iter()
318 .map(|row| Setting {
319 key: row.get("key"),
320 value: row.get("value"),
321 updated_at: row.get("updated_at"),
322 })
323 .collect();
324
325 Ok(settings)
326}
327
328pub async fn upsert_setting(pool: &SqlitePool, key: &str, value: &str) -> Result<(), sqlx::Error> {
329 let mut conn = pool.acquire().await?;
330 sqlx::query(
331 "INSERT INTO settings (key, value, updated_at)
332 VALUES (?, ?, CURRENT_TIMESTAMP)
333 ON CONFLICT(key) DO UPDATE SET
334 value = excluded.value,
335 updated_at = CURRENT_TIMESTAMP",
336 )
337 .bind(key)
338 .bind(value)
339 .execute(&mut *conn)
340 .await?;
341 Ok(())
342}
343
344pub async fn delete_setting_with_mode(
346 key: &str, mode: DatabaseMode,
347) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
348 let context = DatabaseManager::get_context(mode).await?;
349 let mut conn = context.pool.acquire().await?;
350 sqlx::query("DELETE FROM settings WHERE key = ?")
351 .bind(key)
352 .execute(&mut *conn)
353 .await?;
354 Ok(())
355}
356
357pub async fn get_settings_with_prefix_and_mode(
360 prefix: &str, mode: DatabaseMode,
361) -> Result<Vec<(String, String)>, Box<dyn std::error::Error + Send + Sync>> {
362 let context = DatabaseManager::get_context(mode).await?;
363 let mut conn = context.pool.acquire().await?;
364 let escaped = prefix
367 .replace('\\', "\\\\")
368 .replace('%', "\\%")
369 .replace('_', "\\_");
370 let rows = sqlx::query("SELECT key, value FROM settings WHERE key LIKE ? ESCAPE '\\'")
371 .bind(format!("{escaped}%"))
372 .fetch_all(&mut *conn)
373 .await?;
374
375 Ok(rows
376 .iter()
377 .map(|row| {
378 (
379 sqlx::Row::get::<String, _>(row, "key"),
380 sqlx::Row::get::<String, _>(row, "value"),
381 )
382 })
383 .filter(|(key, _)| key.starts_with(prefix))
384 .collect())
385}
386
387pub async fn get_setting(
388 key: &str,
389) -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> {
390 let pool = get_db_pool().await?;
391 get_setting_with_pool(&pool, key).await
392}
393
394pub async fn get_setting_with_pool(
395 pool: &SqlitePool, key: &str,
396) -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> {
397 let mut conn = pool.acquire().await?;
398 let result = sqlx::query("SELECT value FROM settings WHERE key = ?")
399 .bind(key)
400 .fetch_optional(&mut *conn)
401 .await?;
402
403 Ok(result.map(|row| row.get("value")))
404}
405
406pub async fn set_setting(
407 key: &str, value: &str,
408) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
409 let pool = get_db_pool().await?;
410 upsert_setting(&pool, key, value).await?;
411 Ok(())
412}
413
414pub async fn get_disconnect_timeout()
415-> Result<Option<u32>, Box<dyn std::error::Error + Send + Sync>> {
416 if let Some(value) = get_setting("disconnect_timeout_minutes").await? {
417 Ok(value.parse::<u32>().ok())
418 } else {
419 Ok(Some(0)) }
421}
422
423pub async fn set_disconnect_timeout(
424 minutes: u32,
425) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
426 set_setting("disconnect_timeout_minutes", &minutes.to_string()).await
427}
428
429pub async fn get_network_monitor() -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
430 if let Some(value) = get_setting("network_monitor").await? {
431 Ok(value.parse::<bool>().unwrap_or(true))
432 } else {
433 Ok(true) }
435}
436
437pub async fn set_network_monitor(
438 enabled: bool,
439) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
440 set_setting("network_monitor", &enabled.to_string()).await
441}
442
443pub async fn get_setting_with_mode(
444 key: &str, mode: DatabaseMode,
445) -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> {
446 let context = DatabaseManager::get_context(mode).await?;
447 let mut conn = context.pool.acquire().await?;
448 let result = sqlx::query("SELECT value FROM settings WHERE key = ?")
449 .bind(key)
450 .fetch_optional(&mut *conn)
451 .await?;
452 Ok(result.map(|row| row.get("value")))
453}
454
455pub async fn set_setting_with_mode(
456 key: &str, value: &str, mode: DatabaseMode,
457) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
458 let context = DatabaseManager::get_context(mode).await?;
459 upsert_setting(&context.pool, key, value).await?;
460 Ok(())
461}
462
463pub async fn increment_setting_with_mode(
473 key: &str, mode: DatabaseMode,
474) -> Result<u32, Box<dyn std::error::Error + Send + Sync>> {
475 let context = DatabaseManager::get_context(mode).await?;
476 let mut conn = context.pool.acquire().await?;
477 let row = sqlx::query(
478 "INSERT INTO settings (key, value, updated_at)
479 VALUES (?, '1', CURRENT_TIMESTAMP)
480 ON CONFLICT(key) DO UPDATE SET
481 value = CAST(value AS INTEGER) + 1,
482 updated_at = CURRENT_TIMESTAMP
483 WHERE value NOT GLOB '*[^0-9]*' AND value != '' AND CAST(value AS INTEGER) < 4294967295
484 RETURNING value",
485 )
486 .bind(key)
487 .fetch_optional(&mut *conn)
488 .await?;
489 let Some(row) = row else {
490 return Err(format!(
491 "setting {key} holds a non-numeric value, or is already at its maximum, and cannot \
492 be incremented"
493 )
494 .into());
495 };
496 let value: String = row.get("value");
497 Ok(value.parse()?)
498}
499
500pub async fn get_disconnect_timeout_with_mode(
501 mode: DatabaseMode,
502) -> Result<Option<u32>, Box<dyn std::error::Error + Send + Sync>> {
503 if let Some(value) = get_setting_with_mode("disconnect_timeout_minutes", mode).await? {
504 Ok(value.parse::<u32>().ok())
505 } else {
506 Ok(Some(0))
507 }
508}
509
510pub async fn set_disconnect_timeout_with_mode(
511 minutes: u32, mode: DatabaseMode,
512) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
513 set_setting_with_mode("disconnect_timeout_minutes", &minutes.to_string(), mode).await
514}
515
516pub async fn get_network_monitor_with_mode(
517 mode: DatabaseMode,
518) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
519 if let Some(value) = get_setting_with_mode("network_monitor", mode).await? {
520 Ok(value.parse::<bool>().unwrap_or(true))
521 } else {
522 Ok(true)
523 }
524}
525
526pub async fn set_network_monitor_with_mode(
527 enabled: bool, mode: DatabaseMode,
528) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
529 set_setting_with_mode("network_monitor", &enabled.to_string(), mode).await
530}
531
532pub async fn get_auto_update_enabled() -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
533 if let Some(value) = get_setting("auto_update_enabled").await? {
534 Ok(value.parse::<bool>().unwrap_or(true))
535 } else {
536 Ok(true)
537 }
538}
539
540pub async fn set_auto_update_enabled(
541 enabled: bool,
542) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
543 set_setting("auto_update_enabled", &enabled.to_string()).await
544}
545
546pub async fn get_last_update_check() -> Result<Option<i64>, Box<dyn std::error::Error + Send + Sync>>
547{
548 if let Some(value) = get_setting("last_update_check").await? {
549 Ok(value.parse::<i64>().ok())
550 } else {
551 Ok(None)
552 }
553}
554
555pub async fn set_last_update_check(
556 timestamp: i64,
557) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
558 set_setting("last_update_check", ×tamp.to_string()).await
559}
560
561pub async fn get_auto_update_enabled_with_mode(
562 mode: DatabaseMode,
563) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
564 if let Some(value) = get_setting_with_mode("auto_update_enabled", mode).await? {
565 Ok(value.parse::<bool>().unwrap_or(true))
566 } else {
567 Ok(true)
568 }
569}
570
571pub async fn set_auto_update_enabled_with_mode(
572 enabled: bool, mode: DatabaseMode,
573) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
574 set_setting_with_mode("auto_update_enabled", &enabled.to_string(), mode).await
575}
576
577pub async fn get_app_settings() -> Result<AppSettings, Box<dyn std::error::Error + Send + Sync>> {
578 let pool = get_db_pool().await?;
579 let settings = load_all_settings(&pool).await?;
580
581 let settings_map: HashMap<String, String> =
582 settings.into_iter().map(|s| (s.key, s.value)).collect();
583
584 Ok(AppSettings::from_settings_manager(&settings_map))
585}
586
587pub async fn set_app_settings(
588 app_settings: &AppSettings,
589) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
590 let pool = get_db_pool().await?;
591 let settings_map = app_settings.to_settings_map();
592
593 for (key, value) in settings_map.iter() {
594 upsert_setting(&pool, key, value).await?;
595 }
596
597 Ok(())
598}
599
600pub async fn get_ssl_enabled() -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
601 if let Some(value) = get_setting("ssl_enabled").await? {
602 Ok(value.parse::<bool>().unwrap_or(false))
603 } else {
604 Ok(false)
605 }
606}
607
608pub async fn set_ssl_enabled(
609 enabled: bool,
610) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
611 set_setting("ssl_enabled", &enabled.to_string()).await
612}
613
614pub async fn get_ssl_cert_validity_days() -> Result<u16, Box<dyn std::error::Error + Send + Sync>> {
615 if let Some(value) = get_setting("ssl_cert_validity_days").await? {
616 Ok(value.parse::<u16>().unwrap_or(365))
617 } else {
618 Ok(365)
619 }
620}
621
622pub async fn set_ssl_cert_validity_days(
623 days: u16,
624) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
625 set_setting("ssl_cert_validity_days", &days.to_string()).await
626}
627
628pub async fn get_ssl_auto_regenerate() -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
629 if let Some(value) = get_setting("ssl_auto_regenerate").await? {
630 Ok(value.parse::<bool>().unwrap_or(true))
631 } else {
632 Ok(true)
633 }
634}
635
636pub async fn set_ssl_auto_regenerate(
637 enabled: bool,
638) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
639 set_setting("ssl_auto_regenerate", &enabled.to_string()).await
640}
641
642pub async fn get_ssl_ca_auto_install() -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
643 if let Some(value) = get_setting("ssl_ca_auto_install").await? {
644 Ok(value.parse::<bool>().unwrap_or(false))
645 } else {
646 Ok(false)
647 }
648}
649
650pub async fn set_ssl_ca_auto_install(
651 enabled: bool,
652) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
653 set_setting("ssl_ca_auto_install", &enabled.to_string()).await
654}
655
656pub async fn get_global_shortcut() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
657 if let Some(value) = get_setting("global_shortcut").await? {
658 Ok(value)
659 } else {
660 Ok("Ctrl+Shift+F1".to_string())
661 }
662}
663
664pub async fn set_global_shortcut(
665 shortcut: &str,
666) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
667 set_setting("global_shortcut", shortcut).await
668}
669
670pub async fn get_mcp_server_enabled() -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
671 if let Some(value) = get_setting("mcp_server_enabled").await? {
672 Ok(value.parse::<bool>().unwrap_or(false))
673 } else {
674 Ok(false)
675 }
676}
677
678pub async fn set_mcp_server_enabled(
679 enabled: bool,
680) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
681 set_setting("mcp_server_enabled", &enabled.to_string()).await
682}
683
684pub async fn get_mcp_server_port() -> Result<u16, Box<dyn std::error::Error + Send + Sync>> {
685 if let Some(value) = get_setting("mcp_server_port").await? {
686 Ok(value.parse::<u16>().unwrap_or(3000))
687 } else {
688 Ok(3000)
689 }
690}
691
692pub async fn set_mcp_server_port(
693 port: u16,
694) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
695 set_setting("mcp_server_port", &port.to_string()).await
696}
697
698pub async fn get_ssl_enabled_with_mode(
699 mode: DatabaseMode,
700) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
701 if let Some(value) = get_setting_with_mode("ssl_enabled", mode).await? {
702 Ok(value.parse::<bool>().unwrap_or(false))
703 } else {
704 Ok(false)
705 }
706}
707
708pub async fn set_ssl_enabled_with_mode(
709 enabled: bool, mode: DatabaseMode,
710) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
711 set_setting_with_mode("ssl_enabled", &enabled.to_string(), mode).await
712}
713
714pub async fn get_ssl_cert_validity_days_with_mode(
715 mode: DatabaseMode,
716) -> Result<u16, Box<dyn std::error::Error + Send + Sync>> {
717 if let Some(value) = get_setting_with_mode("ssl_cert_validity_days", mode).await? {
718 Ok(value.parse::<u16>().unwrap_or(365))
719 } else {
720 Ok(365)
721 }
722}
723
724pub async fn set_ssl_cert_validity_days_with_mode(
725 days: u16, mode: DatabaseMode,
726) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
727 set_setting_with_mode("ssl_cert_validity_days", &days.to_string(), mode).await
728}
729
730pub async fn get_ssl_auto_regenerate_with_mode(
731 mode: DatabaseMode,
732) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
733 if let Some(value) = get_setting_with_mode("ssl_auto_regenerate", mode).await? {
734 Ok(value.parse::<bool>().unwrap_or(true))
735 } else {
736 Ok(true)
737 }
738}
739
740pub async fn set_ssl_auto_regenerate_with_mode(
741 enabled: bool, mode: DatabaseMode,
742) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
743 set_setting_with_mode("ssl_auto_regenerate", &enabled.to_string(), mode).await
744}
745
746pub async fn get_ssl_ca_auto_install_with_mode(
747 mode: DatabaseMode,
748) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
749 if let Some(value) = get_setting_with_mode("ssl_ca_auto_install", mode).await? {
750 Ok(value.parse::<bool>().unwrap_or(false))
751 } else {
752 Ok(false)
753 }
754}
755
756pub async fn set_ssl_ca_auto_install_with_mode(
757 enabled: bool, mode: DatabaseMode,
758) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
759 set_setting_with_mode("ssl_ca_auto_install", &enabled.to_string(), mode).await
760}
761
762pub async fn get_app_settings_with_mode(
763 mode: DatabaseMode,
764) -> Result<AppSettings, Box<dyn std::error::Error + Send + Sync>> {
765 let context = DatabaseManager::get_context(mode).await?;
766 let settings = load_all_settings(&context.pool).await?;
767
768 let settings_map: HashMap<String, String> =
769 settings.into_iter().map(|s| (s.key, s.value)).collect();
770
771 Ok(AppSettings::from_settings_manager(&settings_map))
772}
773
774pub fn mode_scope(mode: DatabaseMode) -> &'static str {
776 match mode {
777 DatabaseMode::File => "file",
778 DatabaseMode::Memory => "memory",
779 }
780}
781
782pub fn expose_legacy_key(config_id: &str, mode: DatabaseMode) -> String {
785 format!("expose_legacy_possible:{}:{config_id}", mode_scope(mode))
786}
787
788pub fn expose_history_baseline_key(mode: DatabaseMode) -> String {
790 format!("expose_history_baseline:{}", mode_scope(mode))
791}
792
793fn expose_baseline_pending_ids_key(mode: DatabaseMode) -> String {
799 format!("expose_baseline_pending_ids:{}", mode_scope(mode))
800}
801
802static FAILED_BASELINE_SNAPSHOT: LazyLock<Mutex<HashMap<DatabaseMode, HashSet<i64>>>> =
811 LazyLock::new(|| Mutex::new(HashMap::new()));
812
813const BASELINE_INIT_ATTEMPTS: u32 = 3;
814const BASELINE_INIT_RETRY_DELAY: std::time::Duration = std::time::Duration::from_millis(50);
815
816pub async fn establish_expose_history_baseline_at_init(
824 pool: &SqlitePool, mode: DatabaseMode,
825) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
826 let mut last_error = None;
827 for attempt in 0..BASELINE_INIT_ATTEMPTS {
828 if attempt > 0 {
829 tokio::time::sleep(BASELINE_INIT_RETRY_DELAY * attempt).await;
830 }
831 match establish_expose_history_baseline(pool, mode).await {
832 Ok(()) => return Ok(()),
833 Err(error) => last_error = Some(error),
834 }
835 }
836
837 if let Ok(rows) = sqlx::query("SELECT id FROM configs").fetch_all(pool).await {
838 let ids: HashSet<i64> = rows
839 .iter()
840 .filter_map(|row| row.try_get("id").ok())
841 .collect();
842 let serialized =
843 serde_json::to_string(&ids.iter().copied().collect::<Vec<_>>()).unwrap_or_default();
844 FAILED_BASELINE_SNAPSHOT.lock().unwrap().insert(mode, ids);
845 if let Err(error) =
846 upsert_setting(pool, &expose_baseline_pending_ids_key(mode), &serialized).await
847 {
848 warn!(
849 "failed to persist the expose baseline pending id allow-list: {error}; \
850 relying on the in-memory snapshot for this process only"
851 );
852 }
853 }
854 Err(last_error.expect("loop runs at least once"))
859}
860
861pub async fn establish_expose_history_baseline(
862 pool: &SqlitePool, mode: DatabaseMode,
863) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
864 let baseline = expose_history_baseline_key(mode);
865 if sqlx::query("SELECT 1 FROM settings WHERE key = ?")
870 .bind(&baseline)
871 .fetch_optional(pool)
872 .await?
873 .is_some()
874 {
875 return Ok(());
876 }
877 let mut tx = pool.begin_with("BEGIN IMMEDIATE").await?;
887 let in_memory_snapshot = FAILED_BASELINE_SNAPSHOT.lock().unwrap().get(&mode).cloned();
888 let allowed_ids = match in_memory_snapshot {
889 Some(ids) => Some(ids),
890 None => read_pending_baseline_ids(&mut tx, mode).await?,
895 };
896 take_expose_history_baseline(&mut tx, &baseline, mode, allowed_ids.as_ref()).await?;
897 clear_pending_baseline_ids(&mut tx, mode).await?;
898 tx.commit().await?;
899 Ok(())
900}
901
902async fn read_pending_baseline_ids(
903 conn: &mut sqlx::SqliteConnection, mode: DatabaseMode,
904) -> Result<Option<HashSet<i64>>, Box<dyn std::error::Error + Send + Sync>> {
905 let row = sqlx::query("SELECT value FROM settings WHERE key = ?")
906 .bind(expose_baseline_pending_ids_key(mode))
907 .fetch_optional(&mut *conn)
908 .await?;
909 let Some(row) = row else {
910 return Ok(None);
911 };
912 let value: String = row.try_get("value")?;
913 let ids: Vec<i64> = serde_json::from_str(&value)?;
914 Ok(Some(ids.into_iter().collect()))
915}
916
917async fn clear_pending_baseline_ids(
921 conn: &mut sqlx::SqliteConnection, mode: DatabaseMode,
922) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
923 sqlx::query("DELETE FROM settings WHERE key = ?")
924 .bind(expose_baseline_pending_ids_key(mode))
925 .execute(&mut *conn)
926 .await?;
927 Ok(())
928}
929
930async fn take_expose_history_baseline(
931 conn: &mut sqlx::SqliteConnection, baseline: &str, mode: DatabaseMode,
932 allowed_ids: Option<&HashSet<i64>>,
933) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
934 const UPSERT: &str = "INSERT INTO settings (key, value, updated_at) VALUES (?, '1', \
935 CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = \
936 excluded.value, updated_at = CURRENT_TIMESTAMP";
937
938 let taken = sqlx::query("SELECT value FROM settings WHERE key = ?")
939 .bind(baseline)
940 .fetch_optional(&mut *conn)
941 .await?;
942 if taken.is_some() {
943 return Ok(());
944 }
945 let rows = sqlx::query("SELECT id, data FROM configs")
946 .fetch_all(&mut *conn)
947 .await?;
948 for row in rows {
949 let id: i64 = row.try_get("id")?;
950 if allowed_ids.is_some_and(|allowed| !allowed.contains(&id)) {
951 continue;
952 }
953 let data: String = row.try_get("data")?;
954 let is_exposure = match serde_json::from_str::<serde_json::Value>(&data) {
960 Ok(value) => {
961 value.get("workload_type").and_then(|kind| kind.as_str()) == Some("expose")
962 }
963 Err(_) => true,
964 };
965 if !is_exposure {
966 continue;
967 }
968 sqlx::query(UPSERT)
969 .bind(expose_legacy_key(&id.to_string(), mode))
970 .execute(&mut *conn)
971 .await?;
972 }
973 sqlx::query(UPSERT)
974 .bind(baseline)
975 .execute(&mut *conn)
976 .await?;
977 Ok(())
978}
979
980#[cfg(test)]
981mod tests {
982 use sqlx::SqlitePool;
983
984 use super::*;
985 use crate::utils::db::create_db_table;
986
987 async fn create_test_pool() -> SqlitePool {
988 let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
989 create_db_table(&pool).await.unwrap();
990 pool
991 }
992
993 #[tokio::test]
994 async fn test_upsert_setting() {
995 let pool = create_test_pool().await;
996
997 upsert_setting(&pool, "test_key", "test_value")
998 .await
999 .unwrap();
1000
1001 let result: String = sqlx::query("SELECT value FROM settings WHERE key = 'test_key'")
1002 .fetch_one(&pool)
1003 .await
1004 .unwrap()
1005 .get("value");
1006 assert_eq!(result, "test_value");
1007
1008 upsert_setting(&pool, "test_key", "updated_value")
1009 .await
1010 .unwrap();
1011
1012 let result: String = sqlx::query("SELECT value FROM settings WHERE key = 'test_key'")
1013 .fetch_one(&pool)
1014 .await
1015 .unwrap()
1016 .get("value");
1017 assert_eq!(result, "updated_value");
1018 }
1019
1020 #[tokio::test]
1021 async fn test_load_all_settings() {
1022 let pool = create_test_pool().await;
1023
1024 upsert_setting(&pool, "key1", "value1").await.unwrap();
1025 upsert_setting(&pool, "key2", "value2").await.unwrap();
1026
1027 let settings = load_all_settings(&pool).await.unwrap();
1028
1029 assert_eq!(settings.len(), 2);
1030 assert_eq!(settings[0].key, "key1");
1031 assert_eq!(settings[0].value, "value1");
1032 assert_eq!(settings[1].key, "key2");
1033 assert_eq!(settings[1].value, "value2");
1034 }
1035
1036 #[tokio::test]
1037 async fn test_settings_manager() {
1038 let pool = create_test_pool().await;
1039
1040 upsert_setting(&pool, "test_timeout", "30").await.unwrap();
1041 let settings = load_all_settings(&pool).await.unwrap();
1042 assert_eq!(settings.len(), 1);
1043 assert_eq!(settings[0].key, "test_timeout");
1044 assert_eq!(settings[0].value, "30");
1045
1046 upsert_setting(&pool, "test_timeout", "60").await.unwrap();
1047 let settings = load_all_settings(&pool).await.unwrap();
1048 assert_eq!(settings.len(), 1);
1049 assert_eq!(settings[0].value, "60");
1050
1051 let _manager = SettingsManager::new();
1052 }
1053
1054 #[tokio::test]
1055 async fn test_settings_with_mode_memory() {
1056 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1057 set_setting_with_mode("memory_test", "test_value", DatabaseMode::Memory)
1058 .await
1059 .unwrap();
1060
1061 let value = get_setting_with_mode("memory_test", DatabaseMode::Memory)
1062 .await
1063 .unwrap();
1064 assert_eq!(value, Some("test_value".to_string()));
1065
1066 set_disconnect_timeout_with_mode(120, DatabaseMode::Memory)
1067 .await
1068 .unwrap();
1069 let timeout = get_disconnect_timeout_with_mode(DatabaseMode::Memory)
1070 .await
1071 .unwrap();
1072 assert_eq!(timeout, Some(120));
1073
1074 set_network_monitor_with_mode(false, DatabaseMode::Memory)
1075 .await
1076 .unwrap();
1077 let monitor = get_network_monitor_with_mode(DatabaseMode::Memory)
1078 .await
1079 .unwrap();
1080 assert!(!monitor);
1081 }
1082
1083 #[tokio::test]
1084 async fn test_settings_isolation_between_modes() {
1085 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1086 let memory_context = DatabaseManager::get_context(DatabaseMode::Memory)
1087 .await
1088 .unwrap();
1089 let memory_key = "isolation_test_memory";
1090
1091 upsert_setting(&memory_context.pool, memory_key, "memory_value")
1092 .await
1093 .unwrap();
1094
1095 let memory_result = get_setting_with_pool(&memory_context.pool, memory_key)
1096 .await
1097 .unwrap();
1098 assert_eq!(memory_result, Some("memory_value".to_string()));
1099
1100 let different_key = "isolation_test_different";
1101 let different_result = get_setting_with_pool(&memory_context.pool, different_key)
1102 .await
1103 .unwrap();
1104 assert!(
1105 different_result.is_none(),
1106 "Different key should not have data"
1107 );
1108
1109 let same_result = get_setting_with_pool(&memory_context.pool, memory_key)
1110 .await
1111 .unwrap();
1112 assert_eq!(same_result, Some("memory_value".to_string()));
1113 }
1114
1115 #[tokio::test]
1116 async fn test_lazy_baseline_never_marks_row_created_after_failed_init_memory_mode() {
1117 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1118
1119 let context = DatabaseManager::get_context(DatabaseMode::Memory)
1120 .await
1121 .unwrap();
1122 let pool = context.pool.clone();
1123
1124 crate::utils::config::delete_all_configs_with_pool(&pool)
1125 .await
1126 .unwrap();
1127 let _ = delete_setting_with_mode(
1128 &expose_history_baseline_key(DatabaseMode::Memory),
1129 DatabaseMode::Memory,
1130 )
1131 .await;
1132
1133 let existing_config = crate::models::config_model::Config {
1134 workload_type: Some("expose".to_string()),
1135 ..Default::default()
1136 };
1137 let existing_id = crate::utils::config::insert_config_with_pool_and_mode(
1138 existing_config,
1139 &pool,
1140 DatabaseMode::Memory,
1141 )
1142 .await
1143 .unwrap();
1144
1145 FAILED_BASELINE_SNAPSHOT
1148 .lock()
1149 .unwrap()
1150 .insert(DatabaseMode::Memory, HashSet::from([existing_id]));
1151
1152 let created_after_failed_init = crate::models::config_model::Config {
1153 workload_type: Some("expose".to_string()),
1154 ..Default::default()
1155 };
1156 let after_id = crate::utils::config::insert_config_with_pool_and_mode(
1157 created_after_failed_init,
1158 &pool,
1159 DatabaseMode::Memory,
1160 )
1161 .await
1162 .unwrap();
1163
1164 establish_expose_history_baseline(&pool, DatabaseMode::Memory)
1165 .await
1166 .unwrap();
1167
1168 let existing_marked = get_setting_with_mode(
1169 &expose_legacy_key(&existing_id.to_string(), DatabaseMode::Memory),
1170 DatabaseMode::Memory,
1171 )
1172 .await
1173 .unwrap();
1174 assert!(
1175 existing_marked.is_some(),
1176 "a config that existed at the failed-init snapshot must be marked legacy"
1177 );
1178
1179 let after_marked = get_setting_with_mode(
1180 &expose_legacy_key(&after_id.to_string(), DatabaseMode::Memory),
1181 DatabaseMode::Memory,
1182 )
1183 .await
1184 .unwrap();
1185 assert!(
1186 after_marked.is_none(),
1187 "a config created after the failed init must never be marked legacy"
1188 );
1189
1190 FAILED_BASELINE_SNAPSHOT
1191 .lock()
1192 .unwrap()
1193 .remove(&DatabaseMode::Memory);
1194 let _ = delete_setting_with_mode(
1195 &expose_history_baseline_key(DatabaseMode::Memory),
1196 DatabaseMode::Memory,
1197 )
1198 .await;
1199 let _ = crate::utils::config::delete_all_configs_with_pool(&pool).await;
1200 }
1201
1202 #[tokio::test]
1203 async fn test_failed_snapshot_query_leaves_lazy_baseline_unrestricted_memory_mode() {
1204 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1205
1206 let context = DatabaseManager::get_context(DatabaseMode::Memory)
1207 .await
1208 .unwrap();
1209 let pool = context.pool.clone();
1210
1211 crate::utils::config::delete_all_configs_with_pool(&pool)
1212 .await
1213 .unwrap();
1214 let _ = delete_setting_with_mode(
1215 &expose_history_baseline_key(DatabaseMode::Memory),
1216 DatabaseMode::Memory,
1217 )
1218 .await;
1219 FAILED_BASELINE_SNAPSHOT
1220 .lock()
1221 .unwrap()
1222 .remove(&DatabaseMode::Memory);
1223
1224 sqlx::query("DROP TABLE configs")
1227 .execute(&*pool)
1228 .await
1229 .unwrap();
1230
1231 let init_result =
1232 establish_expose_history_baseline_at_init(&pool, DatabaseMode::Memory).await;
1233 assert!(
1234 init_result.is_err(),
1235 "every attempt must fail with no configs table"
1236 );
1237 assert!(
1238 FAILED_BASELINE_SNAPSHOT
1239 .lock()
1240 .unwrap()
1241 .get(&DatabaseMode::Memory)
1242 .is_none(),
1243 "a failed snapshot query must not record an empty allow-list"
1244 );
1245
1246 crate::utils::db::create_db_table(&pool).await.unwrap();
1249
1250 let pre_existing = crate::models::config_model::Config {
1251 workload_type: Some("expose".to_string()),
1252 ..Default::default()
1253 };
1254 let pre_existing_id = crate::utils::config::insert_config_with_pool_and_mode(
1255 pre_existing,
1256 &pool,
1257 DatabaseMode::Memory,
1258 )
1259 .await
1260 .unwrap();
1261
1262 establish_expose_history_baseline(&pool, DatabaseMode::Memory)
1263 .await
1264 .unwrap();
1265
1266 let marked = get_setting_with_mode(
1267 &expose_legacy_key(&pre_existing_id.to_string(), DatabaseMode::Memory),
1268 DatabaseMode::Memory,
1269 )
1270 .await
1271 .unwrap();
1272 assert!(
1273 marked.is_some(),
1274 "the lazy baseline must mark a pre-existing exposure when init's snapshot failed"
1275 );
1276
1277 FAILED_BASELINE_SNAPSHOT
1278 .lock()
1279 .unwrap()
1280 .remove(&DatabaseMode::Memory);
1281 let _ = delete_setting_with_mode(
1282 &expose_history_baseline_key(DatabaseMode::Memory),
1283 DatabaseMode::Memory,
1284 )
1285 .await;
1286 let _ = crate::utils::config::delete_all_configs_with_pool(&pool).await;
1287 }
1288
1289 #[tokio::test]
1290 async fn test_snapshot_is_recorded_in_memory_even_when_persisting_it_fails_memory_mode() {
1291 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1292
1293 let context = DatabaseManager::get_context(DatabaseMode::Memory)
1294 .await
1295 .unwrap();
1296 let pool = context.pool.clone();
1297
1298 crate::utils::config::delete_all_configs_with_pool(&pool)
1299 .await
1300 .unwrap();
1301 let _ = delete_setting_with_mode(
1302 &expose_history_baseline_key(DatabaseMode::Memory),
1303 DatabaseMode::Memory,
1304 )
1305 .await;
1306 FAILED_BASELINE_SNAPSHOT
1307 .lock()
1308 .unwrap()
1309 .remove(&DatabaseMode::Memory);
1310
1311 let existing_config = crate::models::config_model::Config {
1312 workload_type: Some("expose".to_string()),
1313 ..Default::default()
1314 };
1315 let existing_id = crate::utils::config::insert_config_with_pool_and_mode(
1316 existing_config,
1317 &pool,
1318 DatabaseMode::Memory,
1319 )
1320 .await
1321 .unwrap();
1322
1323 sqlx::query("DROP TABLE settings")
1328 .execute(&*pool)
1329 .await
1330 .unwrap();
1331
1332 let init_result =
1333 establish_expose_history_baseline_at_init(&pool, DatabaseMode::Memory).await;
1334 assert!(
1335 init_result.is_err(),
1336 "every attempt must fail with no settings table"
1337 );
1338 assert_eq!(
1339 FAILED_BASELINE_SNAPSHOT
1340 .lock()
1341 .unwrap()
1342 .get(&DatabaseMode::Memory)
1343 .cloned(),
1344 Some(HashSet::from([existing_id])),
1345 "the in-memory snapshot must be recorded even though persisting the allow-list \
1346 failed"
1347 );
1348
1349 crate::utils::db::create_db_table(&pool).await.unwrap();
1351
1352 FAILED_BASELINE_SNAPSHOT
1353 .lock()
1354 .unwrap()
1355 .remove(&DatabaseMode::Memory);
1356 let _ = crate::utils::config::delete_all_configs_with_pool(&pool).await;
1357 }
1358
1359 #[tokio::test]
1360 async fn test_persisted_baseline_allow_list_survives_a_restart_memory_mode() {
1361 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1362
1363 let context = DatabaseManager::get_context(DatabaseMode::Memory)
1364 .await
1365 .unwrap();
1366 let pool = context.pool.clone();
1367
1368 crate::utils::config::delete_all_configs_with_pool(&pool)
1369 .await
1370 .unwrap();
1371 let _ = delete_setting_with_mode(
1372 &expose_history_baseline_key(DatabaseMode::Memory),
1373 DatabaseMode::Memory,
1374 )
1375 .await;
1376 let _ = delete_setting_with_mode(
1377 &expose_baseline_pending_ids_key(DatabaseMode::Memory),
1378 DatabaseMode::Memory,
1379 )
1380 .await;
1381 FAILED_BASELINE_SNAPSHOT
1382 .lock()
1383 .unwrap()
1384 .remove(&DatabaseMode::Memory);
1385
1386 let existing_config = crate::models::config_model::Config {
1387 workload_type: Some("expose".to_string()),
1388 ..Default::default()
1389 };
1390 let existing_id = crate::utils::config::insert_config_with_pool_and_mode(
1391 existing_config,
1392 &pool,
1393 DatabaseMode::Memory,
1394 )
1395 .await
1396 .unwrap();
1397
1398 upsert_setting(
1401 &pool,
1402 &expose_baseline_pending_ids_key(DatabaseMode::Memory),
1403 &serde_json::to_string(&[existing_id]).unwrap(),
1404 )
1405 .await
1406 .unwrap();
1407
1408 FAILED_BASELINE_SNAPSHOT
1411 .lock()
1412 .unwrap()
1413 .remove(&DatabaseMode::Memory);
1414
1415 let created_after_failed_init = crate::models::config_model::Config {
1416 workload_type: Some("expose".to_string()),
1417 ..Default::default()
1418 };
1419 let after_id = crate::utils::config::insert_config_with_pool_and_mode(
1420 created_after_failed_init,
1421 &pool,
1422 DatabaseMode::Memory,
1423 )
1424 .await
1425 .unwrap();
1426
1427 establish_expose_history_baseline(&pool, DatabaseMode::Memory)
1428 .await
1429 .unwrap();
1430
1431 let existing_marked = get_setting_with_mode(
1432 &expose_legacy_key(&existing_id.to_string(), DatabaseMode::Memory),
1433 DatabaseMode::Memory,
1434 )
1435 .await
1436 .unwrap();
1437 assert!(
1438 existing_marked.is_some(),
1439 "a config recorded in the persisted allow-list must be marked legacy after a restart"
1440 );
1441
1442 let after_marked = get_setting_with_mode(
1443 &expose_legacy_key(&after_id.to_string(), DatabaseMode::Memory),
1444 DatabaseMode::Memory,
1445 )
1446 .await
1447 .unwrap();
1448 assert!(
1449 after_marked.is_none(),
1450 "a config created after the failed init must never be marked legacy, even after a \
1451 restart lost the in-memory snapshot"
1452 );
1453
1454 let pending_after = get_setting_with_mode(
1455 &expose_baseline_pending_ids_key(DatabaseMode::Memory),
1456 DatabaseMode::Memory,
1457 )
1458 .await
1459 .unwrap();
1460 assert!(
1461 pending_after.is_none(),
1462 "the persisted allow-list must be deleted once the baseline marker is written"
1463 );
1464
1465 FAILED_BASELINE_SNAPSHOT
1466 .lock()
1467 .unwrap()
1468 .remove(&DatabaseMode::Memory);
1469 let _ = delete_setting_with_mode(
1470 &expose_history_baseline_key(DatabaseMode::Memory),
1471 DatabaseMode::Memory,
1472 )
1473 .await;
1474 let _ = crate::utils::config::delete_all_configs_with_pool(&pool).await;
1475 }
1476
1477 #[tokio::test]
1478 async fn test_malformed_persisted_allow_list_errors_and_leaves_the_pending_key_memory_mode() {
1479 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1480
1481 let context = DatabaseManager::get_context(DatabaseMode::Memory)
1482 .await
1483 .unwrap();
1484 let pool = context.pool.clone();
1485
1486 let _ = delete_setting_with_mode(
1487 &expose_history_baseline_key(DatabaseMode::Memory),
1488 DatabaseMode::Memory,
1489 )
1490 .await;
1491 let _ = delete_setting_with_mode(
1492 &expose_baseline_pending_ids_key(DatabaseMode::Memory),
1493 DatabaseMode::Memory,
1494 )
1495 .await;
1496 FAILED_BASELINE_SNAPSHOT
1497 .lock()
1498 .unwrap()
1499 .remove(&DatabaseMode::Memory);
1500
1501 upsert_setting(
1504 &pool,
1505 &expose_baseline_pending_ids_key(DatabaseMode::Memory),
1506 "not json",
1507 )
1508 .await
1509 .unwrap();
1510
1511 let result = establish_expose_history_baseline(&pool, DatabaseMode::Memory).await;
1512 assert!(
1513 result.is_err(),
1514 "a malformed persisted allow-list must error instead of silently becoming an empty \
1515 list"
1516 );
1517
1518 let pending = get_setting_with_mode(
1519 &expose_baseline_pending_ids_key(DatabaseMode::Memory),
1520 DatabaseMode::Memory,
1521 )
1522 .await
1523 .unwrap();
1524 assert_eq!(
1525 pending,
1526 Some("not json".to_string()),
1527 "a rejected malformed allow-list must be left in place, not cleared"
1528 );
1529
1530 let baseline_taken = get_setting_with_mode(
1531 &expose_history_baseline_key(DatabaseMode::Memory),
1532 DatabaseMode::Memory,
1533 )
1534 .await
1535 .unwrap();
1536 assert!(
1537 baseline_taken.is_none(),
1538 "the baseline marker must not be written when the allow-list read failed"
1539 );
1540
1541 let _ = delete_setting_with_mode(
1542 &expose_baseline_pending_ids_key(DatabaseMode::Memory),
1543 DatabaseMode::Memory,
1544 )
1545 .await;
1546 }
1547
1548 #[tokio::test]
1549 async fn test_increment_setting_with_mode_errors_on_a_non_numeric_value_memory_mode() {
1550 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1551
1552 for junk in ["not-a-number", "12abc"] {
1553 let key = format!("junk_counter_test:{junk}:memory");
1554 let _ = delete_setting_with_mode(&key, DatabaseMode::Memory).await;
1555
1556 let context = DatabaseManager::get_context(DatabaseMode::Memory)
1557 .await
1558 .unwrap();
1559 upsert_setting(&context.pool, &key, junk).await.unwrap();
1560
1561 let result = increment_setting_with_mode(&key, DatabaseMode::Memory).await;
1562 assert!(
1563 result.is_err(),
1564 "a junk counter value ({junk}) must error instead of silently incrementing from a CAST-to-0"
1565 );
1566
1567 let value = get_setting_with_mode(&key, DatabaseMode::Memory)
1568 .await
1569 .unwrap();
1570 assert_eq!(
1571 value,
1572 Some(junk.to_string()),
1573 "a rejected increment must leave the junk value ({junk}) untouched"
1574 );
1575
1576 let _ = delete_setting_with_mode(&key, DatabaseMode::Memory).await;
1577 }
1578 }
1579
1580 #[tokio::test]
1581 async fn test_increment_setting_with_mode_increments_a_numeric_value_memory_mode() {
1582 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1583
1584 let key = "numeric_counter_test:memory";
1585 let _ = delete_setting_with_mode(key, DatabaseMode::Memory).await;
1586
1587 let first = increment_setting_with_mode(key, DatabaseMode::Memory)
1588 .await
1589 .unwrap();
1590 assert_eq!(first, 1);
1591
1592 let second = increment_setting_with_mode(key, DatabaseMode::Memory)
1593 .await
1594 .unwrap();
1595 assert_eq!(second, 2);
1596
1597 let _ = delete_setting_with_mode(key, DatabaseMode::Memory).await;
1598 }
1599
1600 #[tokio::test]
1601 async fn test_increment_setting_with_mode_rejects_an_already_maxed_counter_memory_mode() {
1602 let _lock = crate::test_utils::MEMORY_MODE_TEST_MUTEX.lock().await;
1603
1604 let key = "maxed_counter_test:memory";
1605 let _ = delete_setting_with_mode(key, DatabaseMode::Memory).await;
1606
1607 let context = DatabaseManager::get_context(DatabaseMode::Memory)
1608 .await
1609 .unwrap();
1610 upsert_setting(&context.pool, key, &u32::MAX.to_string())
1611 .await
1612 .unwrap();
1613
1614 let result = increment_setting_with_mode(key, DatabaseMode::Memory).await;
1615 assert!(
1616 result.is_err(),
1617 "a counter already at u32::MAX must be rejected instead of overflowing on increment"
1618 );
1619
1620 let value = get_setting_with_mode(key, DatabaseMode::Memory)
1621 .await
1622 .unwrap();
1623 assert_eq!(
1624 value,
1625 Some(u32::MAX.to_string()),
1626 "a rejected increment must leave the maxed counter untouched"
1627 );
1628
1629 let _ = delete_setting_with_mode(key, DatabaseMode::Memory).await;
1630 }
1631}