mod http;
mod ssdp;
use crate::action::Action;
use crate::mock::{Mock, MockRegistry, ReceivedRequest, ReceivedSsdpRequest};
use crate::responder::Responder;
use crate::Result;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::oneshot;
pub struct MockIgdServer {
http_addr: SocketAddr,
ssdp_addr: Option<SocketAddr>,
registry: Arc<MockRegistry>,
shutdown_tx: Option<oneshot::Sender<()>>,
}
impl MockIgdServer {
pub async fn start() -> Result<Self> {
Self::builder().start().await
}
pub fn builder() -> MockIgdServerBuilder {
MockIgdServerBuilder::default()
}
pub fn url(&self) -> String {
format!("http://{}", self.http_addr)
}
pub fn control_url(&self) -> String {
format!("http://{}/ctl/IPConn", self.http_addr)
}
pub fn description_url(&self) -> String {
format!("http://{}/rootDesc.xml", self.http_addr)
}
pub fn http_addr(&self) -> SocketAddr {
self.http_addr
}
pub fn ssdp_addr(&self) -> Option<SocketAddr> {
self.ssdp_addr
}
pub async fn mock(&self, action: impl Into<Action>, responder: impl Into<Responder>) {
let mock = Mock::new(action, responder);
self.registry.register(mock).await;
}
pub async fn mock_with_priority(
&self,
action: impl Into<Action>,
responder: impl Into<Responder>,
priority: u32,
) {
let mock = Mock::new(action, responder).with_priority(priority);
self.registry.register(mock).await;
}
pub async fn mock_with_times(
&self,
action: impl Into<Action>,
responder: impl Into<Responder>,
times: u32,
) {
let mock = Mock::new(action, responder).times(times);
self.registry.register(mock).await;
}
pub async fn clear_mocks(&self) {
self.registry.clear().await;
}
pub async fn received_requests(&self) -> Vec<ReceivedRequest> {
self.registry.received_requests().await
}
pub async fn clear_received_requests(&self) {
self.registry.clear_received_requests().await;
}
pub async fn received_ssdp_requests(&self) -> Vec<ReceivedSsdpRequest> {
self.registry.received_ssdp_requests().await
}
pub async fn clear_received_ssdp_requests(&self) {
self.registry.clear_received_ssdp_requests().await;
}
pub fn shutdown(mut self) {
if let Some(tx) = self.shutdown_tx.take() {
let _ = tx.send(());
}
}
}
impl Drop for MockIgdServer {
fn drop(&mut self) {
if let Some(tx) = self.shutdown_tx.take() {
let _ = tx.send(());
}
}
}
#[derive(Default)]
pub struct MockIgdServerBuilder {
http_port: Option<u16>,
enable_ssdp: bool,
ssdp_port: Option<u16>,
}
impl MockIgdServerBuilder {
pub fn http_port(mut self, port: u16) -> Self {
self.http_port = Some(port);
self
}
pub fn with_ssdp(mut self) -> Self {
self.enable_ssdp = true;
self
}
pub fn ssdp_port(mut self, port: u16) -> Self {
self.ssdp_port = Some(port);
self.enable_ssdp = true;
self
}
pub async fn start(self) -> Result<MockIgdServer> {
let registry = Arc::new(MockRegistry::new());
let (shutdown_tx, shutdown_rx) = oneshot::channel();
let http_addr = format!("127.0.0.1:{}", self.http_port.unwrap_or(0));
let listener = tokio::net::TcpListener::bind(&http_addr).await?;
let http_addr = listener.local_addr()?;
let http_registry = registry.clone();
tokio::spawn(async move {
http::run_http_server(listener, http_registry, shutdown_rx).await;
});
let ssdp_addr = if self.enable_ssdp {
let port = self.ssdp_port.unwrap_or(1900);
match ssdp::start_ssdp_server(http_addr, port, registry.clone()).await {
Ok(addr) => Some(addr),
Err(e) => {
tracing::warn!("Failed to start SSDP server: {}", e);
None
}
}
} else {
None
};
Ok(MockIgdServer {
http_addr,
ssdp_addr,
registry,
shutdown_tx: Some(shutdown_tx),
})
}
}