1mod http;
4mod ssdp;
5
6use crate::action::Action;
7use crate::mock::{Mock, MockRegistry, ReceivedRequest, ReceivedSsdpRequest};
8use crate::responder::Responder;
9use crate::Result;
10use std::net::SocketAddr;
11use std::sync::Arc;
12use tokio::sync::oneshot;
13
14pub struct MockIgdServer {
16 http_addr: SocketAddr,
18 ssdp_addr: Option<SocketAddr>,
20 registry: Arc<MockRegistry>,
22 shutdown_tx: Option<oneshot::Sender<()>>,
24}
25
26impl MockIgdServer {
27 pub async fn start() -> Result<Self> {
29 Self::builder().start().await
30 }
31
32 pub fn builder() -> MockIgdServerBuilder {
34 MockIgdServerBuilder::default()
35 }
36
37 pub fn url(&self) -> String {
39 format!("http://{}", self.http_addr)
40 }
41
42 pub fn control_url(&self) -> String {
44 format!("http://{}/ctl/IPConn", self.http_addr)
45 }
46
47 pub fn description_url(&self) -> String {
49 format!("http://{}/rootDesc.xml", self.http_addr)
50 }
51
52 pub fn http_addr(&self) -> SocketAddr {
54 self.http_addr
55 }
56
57 pub fn ssdp_addr(&self) -> Option<SocketAddr> {
59 self.ssdp_addr
60 }
61
62 pub async fn mock(&self, action: impl Into<Action>, responder: impl Into<Responder>) {
64 let mock = Mock::new(action, responder);
65 self.registry.register(mock).await;
66 }
67
68 pub async fn mock_with_priority(
70 &self,
71 action: impl Into<Action>,
72 responder: impl Into<Responder>,
73 priority: u32,
74 ) {
75 let mock = Mock::new(action, responder).with_priority(priority);
76 self.registry.register(mock).await;
77 }
78
79 pub async fn mock_with_times(
81 &self,
82 action: impl Into<Action>,
83 responder: impl Into<Responder>,
84 times: u32,
85 ) {
86 let mock = Mock::new(action, responder).times(times);
87 self.registry.register(mock).await;
88 }
89
90 pub async fn clear_mocks(&self) {
92 self.registry.clear().await;
93 }
94
95 pub async fn received_requests(&self) -> Vec<ReceivedRequest> {
108 self.registry.received_requests().await
109 }
110
111 pub async fn clear_received_requests(&self) {
113 self.registry.clear_received_requests().await;
114 }
115
116 pub async fn received_ssdp_requests(&self) -> Vec<ReceivedSsdpRequest> {
129 self.registry.received_ssdp_requests().await
130 }
131
132 pub async fn clear_received_ssdp_requests(&self) {
134 self.registry.clear_received_ssdp_requests().await;
135 }
136
137 pub fn shutdown(mut self) {
139 if let Some(tx) = self.shutdown_tx.take() {
140 let _ = tx.send(());
141 }
142 }
143}
144
145impl Drop for MockIgdServer {
146 fn drop(&mut self) {
147 if let Some(tx) = self.shutdown_tx.take() {
148 let _ = tx.send(());
149 }
150 }
151}
152
153#[derive(Default)]
155pub struct MockIgdServerBuilder {
156 http_port: Option<u16>,
157 enable_ssdp: bool,
158 ssdp_port: Option<u16>,
159}
160
161impl MockIgdServerBuilder {
162 pub fn http_port(mut self, port: u16) -> Self {
164 self.http_port = Some(port);
165 self
166 }
167
168 pub fn with_ssdp(mut self) -> Self {
170 self.enable_ssdp = true;
171 self
172 }
173
174 pub fn ssdp_port(mut self, port: u16) -> Self {
176 self.ssdp_port = Some(port);
177 self.enable_ssdp = true;
178 self
179 }
180
181 pub async fn start(self) -> Result<MockIgdServer> {
183 let registry = Arc::new(MockRegistry::new());
184 let (shutdown_tx, shutdown_rx) = oneshot::channel();
185
186 let http_addr = format!("127.0.0.1:{}", self.http_port.unwrap_or(0));
188 let listener = tokio::net::TcpListener::bind(&http_addr).await?;
189 let http_addr = listener.local_addr()?;
190
191 let http_registry = registry.clone();
192 tokio::spawn(async move {
193 http::run_http_server(listener, http_registry, shutdown_rx).await;
194 });
195
196 let ssdp_addr = if self.enable_ssdp {
198 let port = self.ssdp_port.unwrap_or(1900);
199 match ssdp::start_ssdp_server(http_addr, port, registry.clone()).await {
200 Ok(addr) => Some(addr),
201 Err(e) => {
202 tracing::warn!("Failed to start SSDP server: {}", e);
203 None
204 }
205 }
206 } else {
207 None
208 };
209
210 Ok(MockIgdServer {
211 http_addr,
212 ssdp_addr,
213 registry,
214 shutdown_tx: Some(shutdown_tx),
215 })
216 }
217}