mockforge_tunnel/
manager.rs1use crate::{config::TunnelConfig, provider::*, Result, TunnelError, TunnelStatus};
4use std::sync::Arc;
5use tokio::sync::RwLock;
6
7pub struct TunnelManager {
9 provider: Arc<dyn TunnelProvider>,
10 active_tunnel: Arc<RwLock<Option<TunnelStatus>>>,
11}
12
13impl TunnelManager {
14 pub fn new(config: &TunnelConfig) -> Result<Self> {
16 let provider: Arc<dyn TunnelProvider> = match config.provider {
17 crate::config::TunnelProvider::SelfHosted => {
18 let server_url = config.server_url.clone().ok_or_else(|| {
19 TunnelError::ConfigError(
20 "server_url required for self-hosted provider".to_string(),
21 )
22 })?;
23 Arc::new(SelfHostedProvider::new(server_url, config.auth_token.clone()))
24 }
25 crate::config::TunnelProvider::Cloud => {
26 let server_url = config
29 .server_url
30 .clone()
31 .unwrap_or_else(|| "https://tunnel.mockforge.dev".to_string());
32 Arc::new(SelfHostedProvider::new(server_url, config.auth_token.clone()))
33 }
34 crate::config::TunnelProvider::Cloudflare => {
35 return Err(TunnelError::ProviderError(
36 "Cloudflare tunnel support coming soon".to_string(),
37 ));
38 }
39 crate::config::TunnelProvider::Ngrok => {
40 return Err(TunnelError::ProviderError(
41 "ngrok tunnel support coming soon".to_string(),
42 ));
43 }
44 crate::config::TunnelProvider::Localtunnel => {
45 return Err(TunnelError::ProviderError(
46 "localtunnel support coming soon".to_string(),
47 ));
48 }
49 };
50
51 Ok(Self {
52 provider,
53 active_tunnel: Arc::new(RwLock::new(None)),
54 })
55 }
56
57 pub async fn create_tunnel(&self, config: &TunnelConfig) -> Result<TunnelStatus> {
59 {
61 let tunnel = self.active_tunnel.read().await;
62 if tunnel.is_some() {
63 return Err(TunnelError::AlreadyExists("Tunnel already active".to_string()));
64 }
65 }
66
67 let status = self.provider.create_tunnel(config).await?;
69
70 {
72 let mut tunnel = self.active_tunnel.write().await;
73 *tunnel = Some(status.clone());
74 }
75
76 Ok(status)
77 }
78
79 pub async fn get_status(&self) -> Result<Option<TunnelStatus>> {
81 let tunnel = self.active_tunnel.read().await;
82 Ok(tunnel.clone())
83 }
84
85 pub async fn refresh_status(&self) -> Result<TunnelStatus> {
87 let tunnel_id = {
88 let tunnel = self.active_tunnel.read().await;
89 tunnel
90 .as_ref()
91 .map(|t| t.tunnel_id.clone())
92 .ok_or_else(|| TunnelError::NotFound("No active tunnel".to_string()))?
93 };
94
95 let status = self.provider.get_tunnel_status(&tunnel_id).await?;
96
97 {
99 let mut tunnel = self.active_tunnel.write().await;
100 *tunnel = Some(status.clone());
101 }
102
103 Ok(status)
104 }
105
106 pub async fn stop_tunnel(&self) -> Result<()> {
108 let tunnel_id = {
109 let tunnel = self.active_tunnel.read().await;
110 tunnel
111 .as_ref()
112 .map(|t| t.tunnel_id.clone())
113 .ok_or_else(|| TunnelError::NotFound("No active tunnel".to_string()))?
114 };
115
116 self.provider.delete_tunnel(&tunnel_id).await?;
118
119 {
121 let mut tunnel = self.active_tunnel.write().await;
122 *tunnel = None;
123 }
124
125 Ok(())
126 }
127
128 pub async fn stop_tunnel_by_id(&self, tunnel_id: &str) -> Result<()> {
130 self.provider.delete_tunnel(tunnel_id).await?;
132
133 {
135 let mut tunnel = self.active_tunnel.write().await;
136 if tunnel.as_ref().map(|t| t.tunnel_id.as_str()) == Some(tunnel_id) {
137 *tunnel = None;
138 }
139 }
140
141 Ok(())
142 }
143
144 pub async fn list_tunnels(&self) -> Result<Vec<TunnelStatus>> {
146 self.provider.list_tunnels().await
147 }
148
149 pub async fn is_available(&self) -> bool {
151 self.provider.is_available().await
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::config::TunnelConfig;
159
160 fn create_test_config() -> TunnelConfig {
161 TunnelConfig {
162 provider: crate::config::TunnelProvider::SelfHosted,
163 server_url: Some("https://tunnel.example.com".to_string()),
164 auth_token: Some("test-token".to_string()),
165 subdomain: Some("test".to_string()),
166 local_url: "http://localhost:3000".to_string(),
167 protocol: "http".to_string(),
168 region: None,
169 custom_domain: None,
170 websocket_enabled: true,
171 http2_enabled: true,
172 }
173 }
174
175 #[test]
176 fn test_tunnel_manager_new() {
177 let config = create_test_config();
178 let manager = TunnelManager::new(&config);
179 assert!(manager.is_ok());
180 }
181
182 #[test]
183 fn test_tunnel_manager_new_without_server_url() {
184 let mut config = create_test_config();
185 config.server_url = None;
186
187 let result = TunnelManager::new(&config);
188 assert!(result.is_err());
189
190 if let Err(e) = result {
191 match e {
192 TunnelError::ConfigError(msg) => {
193 assert!(msg.contains("server_url"));
194 }
195 _ => panic!("Expected ConfigError"),
196 }
197 }
198 }
199
200 #[test]
201 fn test_tunnel_manager_new_cloud_provider() {
202 let mut config = create_test_config();
203 config.provider = crate::config::TunnelProvider::Cloud;
204 config.server_url = None; let result = TunnelManager::new(&config);
207 assert!(result.is_ok());
208 }
209
210 #[test]
211 fn test_tunnel_manager_new_cloudflare_provider() {
212 let mut config = create_test_config();
213 config.provider = crate::config::TunnelProvider::Cloudflare;
214
215 let result = TunnelManager::new(&config);
216 assert!(result.is_err());
217
218 if let Err(e) = result {
219 match e {
220 TunnelError::ProviderError(msg) => {
221 assert!(msg.contains("Cloudflare"));
222 assert!(msg.contains("coming soon"));
223 }
224 _ => panic!("Expected ProviderError"),
225 }
226 }
227 }
228
229 #[test]
230 fn test_tunnel_manager_new_ngrok_provider() {
231 let mut config = create_test_config();
232 config.provider = crate::config::TunnelProvider::Ngrok;
233
234 let result = TunnelManager::new(&config);
235 assert!(result.is_err());
236
237 if let Err(e) = result {
238 match e {
239 TunnelError::ProviderError(msg) => {
240 assert!(msg.contains("ngrok"));
241 assert!(msg.contains("coming soon"));
242 }
243 _ => panic!("Expected ProviderError"),
244 }
245 }
246 }
247
248 #[test]
249 fn test_tunnel_manager_new_localtunnel_provider() {
250 let mut config = create_test_config();
251 config.provider = crate::config::TunnelProvider::Localtunnel;
252
253 let result = TunnelManager::new(&config);
254 assert!(result.is_err());
255
256 if let Err(e) = result {
257 match e {
258 TunnelError::ProviderError(msg) => {
259 assert!(msg.contains("localtunnel"));
260 assert!(msg.contains("coming soon"));
261 }
262 _ => panic!("Expected ProviderError"),
263 }
264 }
265 }
266
267 #[tokio::test]
268 async fn test_get_status_no_tunnel() {
269 let config = create_test_config();
270 let manager = TunnelManager::new(&config).unwrap();
271
272 let status = manager.get_status().await;
273 assert!(status.is_ok());
274 assert!(status.unwrap().is_none());
275 }
276
277 #[tokio::test]
278 async fn test_refresh_status_no_tunnel() {
279 let config = create_test_config();
280 let manager = TunnelManager::new(&config).unwrap();
281
282 let result = manager.refresh_status().await;
283 assert!(result.is_err());
284
285 if let Err(e) = result {
286 match e {
287 TunnelError::NotFound(msg) => {
288 assert!(msg.contains("No active tunnel"));
289 }
290 _ => panic!("Expected NotFound error"),
291 }
292 }
293 }
294
295 #[tokio::test]
296 async fn test_stop_tunnel_no_tunnel() {
297 let config = create_test_config();
298 let manager = TunnelManager::new(&config).unwrap();
299
300 let result = manager.stop_tunnel().await;
301 assert!(result.is_err());
302
303 if let Err(e) = result {
304 match e {
305 TunnelError::NotFound(msg) => {
306 assert!(msg.contains("No active tunnel"));
307 }
308 _ => panic!("Expected NotFound error"),
309 }
310 }
311 }
312
313 #[test]
314 fn test_tunnel_manager_with_different_protocols() {
315 let protocols = vec!["http", "https", "ws", "wss"];
316
317 for protocol in protocols {
318 let mut config = create_test_config();
319 config.protocol = protocol.to_string();
320
321 let result = TunnelManager::new(&config);
322 assert!(result.is_ok(), "Failed to create manager with protocol: {}", protocol);
323 }
324 }
325
326 #[test]
327 fn test_tunnel_manager_with_websocket_disabled() {
328 let mut config = create_test_config();
329 config.websocket_enabled = false;
330
331 let result = TunnelManager::new(&config);
332 assert!(result.is_ok());
333 }
334
335 #[test]
336 fn test_tunnel_manager_with_http2_disabled() {
337 let mut config = create_test_config();
338 config.http2_enabled = false;
339
340 let result = TunnelManager::new(&config);
341 assert!(result.is_ok());
342 }
343
344 #[test]
345 fn test_tunnel_manager_with_custom_domain() {
346 let mut config = create_test_config();
347 config.custom_domain = Some("api.example.com".to_string());
348
349 let result = TunnelManager::new(&config);
350 assert!(result.is_ok());
351 }
352
353 #[test]
354 fn test_tunnel_manager_with_region() {
355 let mut config = create_test_config();
356 config.region = Some("us-west".to_string());
357
358 let result = TunnelManager::new(&config);
359 assert!(result.is_ok());
360 }
361
362 #[test]
363 fn test_tunnel_manager_without_auth_token() {
364 let mut config = create_test_config();
365 config.auth_token = None;
366
367 let result = TunnelManager::new(&config);
368 assert!(result.is_ok());
369 }
370
371 #[test]
372 fn test_tunnel_manager_with_different_local_urls() {
373 let urls = vec![
374 "http://localhost:3000",
375 "http://127.0.0.1:8080",
376 "http://0.0.0.0:5000",
377 "https://internal-api:443",
378 ];
379
380 for url in urls {
381 let mut config = create_test_config();
382 config.local_url = url.to_string();
383
384 let result = TunnelManager::new(&config);
385 assert!(result.is_ok(), "Failed to create manager with local_url: {}", url);
386 }
387 }
388
389 #[test]
390 fn test_tunnel_manager_builder_pattern() {
391 let config = TunnelConfig::new("http://localhost:3000")
392 .with_provider(crate::config::TunnelProvider::SelfHosted)
393 .with_auth_token("token123")
394 .with_subdomain("myapp");
395
396 let mut config_with_server = config;
397 config_with_server.server_url = Some("https://tunnel.example.com".to_string());
398
399 let result = TunnelManager::new(&config_with_server);
400 assert!(result.is_ok());
401 }
402
403 #[test]
404 fn test_tunnel_manager_clone_config() {
405 let config = create_test_config();
406 let cloned = config.clone();
407
408 let manager1 = TunnelManager::new(&config);
409 let manager2 = TunnelManager::new(&cloned);
410
411 assert!(manager1.is_ok());
412 assert!(manager2.is_ok());
413 }
414}