ddns_a/network/
fetcher.rs1use super::AdapterSnapshot;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
11pub enum FetchError {
12 #[cfg(windows)]
14 #[error("Windows API error: {0}")]
15 WindowsApi(#[from] windows::core::Error),
16
17 #[error("Permission denied: {context}")]
19 PermissionDenied {
20 context: String,
22 },
23
24 #[error("Platform error: {message}")]
26 Platform {
27 message: String,
29 },
30}
31
32pub trait AddressFetcher: Send + Sync {
58 fn fetch(&self) -> Result<Vec<AdapterSnapshot>, FetchError>;
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::network::{AdapterKind, AdapterSnapshot};
84 use std::sync::Mutex;
85
86 struct MockFetcher {
90 results: Mutex<std::collections::VecDeque<Result<Vec<AdapterSnapshot>, FetchError>>>,
91 }
92
93 impl MockFetcher {
94 fn new(results: Vec<Result<Vec<AdapterSnapshot>, FetchError>>) -> Self {
95 Self {
96 results: Mutex::new(results.into()),
97 }
98 }
99
100 fn returning_snapshots(snapshots: Vec<Vec<AdapterSnapshot>>) -> Self {
101 Self::new(snapshots.into_iter().map(Ok).collect())
102 }
103 }
104
105 impl AddressFetcher for MockFetcher {
106 fn fetch(&self) -> Result<Vec<AdapterSnapshot>, FetchError> {
107 self.results
108 .lock()
109 .unwrap()
110 .pop_front()
111 .unwrap_or_else(|| Ok(vec![]))
112 }
113 }
114
115 #[test]
116 fn mock_fetcher_returns_predefined_snapshots() {
117 let snapshot = AdapterSnapshot::new(
118 "eth0",
119 AdapterKind::Ethernet,
120 vec!["192.168.1.1".parse().unwrap()],
121 vec![],
122 );
123 let fetcher = MockFetcher::returning_snapshots(vec![vec![snapshot.clone()]]);
124
125 let result = fetcher.fetch().unwrap();
126
127 assert_eq!(result.len(), 1);
128 assert_eq!(result[0], snapshot);
129 }
130
131 #[test]
132 fn mock_fetcher_returns_different_results_on_each_call() {
133 let snapshot1 = AdapterSnapshot::new("eth0", AdapterKind::Ethernet, vec![], vec![]);
134 let snapshot2 = AdapterSnapshot::new("eth1", AdapterKind::Wireless, vec![], vec![]);
135
136 let fetcher = MockFetcher::returning_snapshots(vec![vec![snapshot1], vec![snapshot2]]);
137
138 let result1 = fetcher.fetch().unwrap();
139 let result2 = fetcher.fetch().unwrap();
140
141 assert_eq!(result1[0].name, "eth0");
142 assert_eq!(result2[0].name, "eth1");
143 }
144
145 #[test]
146 fn mock_fetcher_returns_empty_after_exhausting_results() {
147 let fetcher = MockFetcher::returning_snapshots(vec![vec![]]);
148
149 let _ = fetcher.fetch(); let result = fetcher.fetch().unwrap(); assert!(result.is_empty());
153 }
154
155 #[test]
156 fn mock_fetcher_can_return_errors() {
157 let fetcher = MockFetcher::new(vec![Err(FetchError::Platform {
158 message: "test error".to_string(),
159 })]);
160
161 let result = fetcher.fetch();
162
163 assert!(result.is_err());
164 let error = result.unwrap_err();
165 assert!(error.to_string().contains("test error"));
166 }
167
168 #[test]
169 fn fetch_error_permission_denied_displays_context() {
170 let error = FetchError::PermissionDenied {
171 context: "elevated privileges required".to_string(),
172 };
173 assert!(error.to_string().contains("elevated privileges required"));
174 }
175
176 #[test]
177 fn fetch_error_platform_displays_message() {
178 let error = FetchError::Platform {
179 message: "unsupported operation".to_string(),
180 };
181 assert!(error.to_string().contains("unsupported operation"));
182 }
183}