test: deepen and complete project-wide test coverage (#297)
* test: deepen coverage for health doctor provider and tunnels * test: add broad trait and module re-export coverage
This commit is contained in:
parent
79a6f180a8
commit
49fcc7a2c4
21 changed files with 1156 additions and 0 deletions
|
|
@ -109,3 +109,33 @@ impl Tunnel for CloudflareTunnel {
|
|||
.and_then(|g| g.as_ref().map(|tp| tp.public_url.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn constructor_stores_token() {
|
||||
let tunnel = CloudflareTunnel::new("cf-token".into());
|
||||
assert_eq!(tunnel.token, "cf-token");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_url_is_none_before_start() {
|
||||
let tunnel = CloudflareTunnel::new("cf-token".into());
|
||||
assert!(tunnel.public_url().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stop_without_started_process_is_ok() {
|
||||
let tunnel = CloudflareTunnel::new("cf-token".into());
|
||||
let result = tunnel.stop().await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn health_check_is_false_before_start() {
|
||||
let tunnel = CloudflareTunnel::new("cf-token".into());
|
||||
assert!(!tunnel.health_check().await);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,3 +143,78 @@ impl Tunnel for CustomTunnel {
|
|||
.and_then(|g| g.as_ref().map(|tp| tp.public_url.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_with_empty_command_returns_error() {
|
||||
let tunnel = CustomTunnel::new(" ".into(), None, None);
|
||||
let result = tunnel.start("127.0.0.1", 8080).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("start_command is empty"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_without_pattern_returns_local_url() {
|
||||
let tunnel = CustomTunnel::new("sleep 1".into(), None, None);
|
||||
|
||||
let url = tunnel.start("127.0.0.1", 4455).await.unwrap();
|
||||
assert_eq!(url, "http://127.0.0.1:4455");
|
||||
assert_eq!(
|
||||
tunnel.public_url().as_deref(),
|
||||
Some("http://127.0.0.1:4455")
|
||||
);
|
||||
|
||||
tunnel.stop().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_with_pattern_extracts_url() {
|
||||
let tunnel = CustomTunnel::new(
|
||||
"echo https://public.example".into(),
|
||||
None,
|
||||
Some("public.example".into()),
|
||||
);
|
||||
|
||||
let url = tunnel.start("localhost", 9999).await.unwrap();
|
||||
|
||||
assert_eq!(url, "https://public.example");
|
||||
assert_eq!(
|
||||
tunnel.public_url().as_deref(),
|
||||
Some("https://public.example")
|
||||
);
|
||||
|
||||
tunnel.stop().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_replaces_host_and_port_placeholders() {
|
||||
let tunnel = CustomTunnel::new(
|
||||
"echo http://{host}:{port}".into(),
|
||||
None,
|
||||
Some("http://".into()),
|
||||
);
|
||||
|
||||
let url = tunnel.start("10.1.2.3", 4321).await.unwrap();
|
||||
|
||||
assert_eq!(url, "http://10.1.2.3:4321");
|
||||
tunnel.stop().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn health_check_with_unreachable_health_url_returns_false() {
|
||||
let tunnel = CustomTunnel::new(
|
||||
"sleep 1".into(),
|
||||
Some("http://127.0.0.1:9/healthz".into()),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!tunnel.health_check().await);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ mod tests {
|
|||
use crate::config::schema::{
|
||||
CloudflareTunnelConfig, CustomTunnelConfig, NgrokTunnelConfig, TunnelConfig,
|
||||
};
|
||||
use tokio::process::Command;
|
||||
|
||||
/// Helper: assert `create_tunnel` returns an error containing `needle`.
|
||||
fn assert_tunnel_err(cfg: &TunnelConfig, needle: &str) {
|
||||
|
|
@ -313,4 +314,62 @@ mod tests {
|
|||
assert_eq!(t.name(), "custom");
|
||||
assert!(t.public_url().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn kill_shared_no_process_is_ok() {
|
||||
let proc = new_shared_process();
|
||||
let result = kill_shared(&proc).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(proc.lock().await.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn kill_shared_terminates_and_clears_child() {
|
||||
let proc = new_shared_process();
|
||||
|
||||
let child = Command::new("sleep")
|
||||
.arg("30")
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn()
|
||||
.expect("sleep should spawn for lifecycle test");
|
||||
|
||||
{
|
||||
let mut guard = proc.lock().await;
|
||||
*guard = Some(TunnelProcess {
|
||||
child,
|
||||
public_url: "https://example.test".into(),
|
||||
});
|
||||
}
|
||||
|
||||
kill_shared(&proc).await.unwrap();
|
||||
|
||||
let guard = proc.lock().await;
|
||||
assert!(guard.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cloudflare_health_false_before_start() {
|
||||
let tunnel = CloudflareTunnel::new("tok".into());
|
||||
assert!(!tunnel.health_check().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ngrok_health_false_before_start() {
|
||||
let tunnel = NgrokTunnel::new("tok".into(), None);
|
||||
assert!(!tunnel.health_check().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tailscale_health_false_before_start() {
|
||||
let tunnel = TailscaleTunnel::new(false, None);
|
||||
assert!(!tunnel.health_check().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn custom_health_false_before_start_without_health_url() {
|
||||
let tunnel = CustomTunnel::new("echo hi".into(), None, Some("https://".into()));
|
||||
assert!(!tunnel.health_check().await);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,3 +119,33 @@ impl Tunnel for NgrokTunnel {
|
|||
.and_then(|g| g.as_ref().map(|tp| tp.public_url.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn constructor_stores_domain() {
|
||||
let tunnel = NgrokTunnel::new("ngrok-token".into(), Some("my.ngrok.app".into()));
|
||||
assert_eq!(tunnel.domain.as_deref(), Some("my.ngrok.app"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_url_is_none_before_start() {
|
||||
let tunnel = NgrokTunnel::new("ngrok-token".into(), None);
|
||||
assert!(tunnel.public_url().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stop_without_started_process_is_ok() {
|
||||
let tunnel = NgrokTunnel::new("ngrok-token".into(), None);
|
||||
let result = tunnel.stop().await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn health_check_is_false_before_start() {
|
||||
let tunnel = NgrokTunnel::new("ngrok-token".into(), None);
|
||||
assert!(!tunnel.health_check().await);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,3 +26,39 @@ impl Tunnel for NoneTunnel {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn name_is_none() {
|
||||
let tunnel = NoneTunnel;
|
||||
assert_eq!(tunnel.name(), "none");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_returns_local_url() {
|
||||
let tunnel = NoneTunnel;
|
||||
let url = tunnel.start("127.0.0.1", 7788).await.unwrap();
|
||||
assert_eq!(url, "http://127.0.0.1:7788");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stop_is_noop_success() {
|
||||
let tunnel = NoneTunnel;
|
||||
assert!(tunnel.stop().await.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn health_check_is_always_true() {
|
||||
let tunnel = NoneTunnel;
|
||||
assert!(tunnel.health_check().await);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_url_is_always_none() {
|
||||
let tunnel = NoneTunnel;
|
||||
assert!(tunnel.public_url().is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,3 +100,34 @@ impl Tunnel for TailscaleTunnel {
|
|||
.and_then(|g| g.as_ref().map(|tp| tp.public_url.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn constructor_stores_hostname_and_mode() {
|
||||
let tunnel = TailscaleTunnel::new(true, Some("myhost.tailnet.ts.net".into()));
|
||||
assert!(tunnel.funnel);
|
||||
assert_eq!(tunnel.hostname.as_deref(), Some("myhost.tailnet.ts.net"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_url_is_none_before_start() {
|
||||
let tunnel = TailscaleTunnel::new(false, None);
|
||||
assert!(tunnel.public_url().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn health_check_is_false_before_start() {
|
||||
let tunnel = TailscaleTunnel::new(false, None);
|
||||
assert!(!tunnel.health_check().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stop_without_started_process_is_ok() {
|
||||
let tunnel = TailscaleTunnel::new(false, None);
|
||||
let result = tunnel.stop().await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue