#[cfg(test)]
mod tests {
use super::super::*;
use bollard::models::{
ContainerConfig, ContainerInspectResponse, ContainerState, EndpointSettings, HostConfig,
NetworkSettings, PortBinding,
};
use std::net::{IpAddr, Ipv4Addr};
use std::sync::Once;
use temp_env::{async_with_vars, with_vars};
static INIT: Once = Once::new();
fn init_test() {
INIT.call_once(|| {
});
}
fn create_test_inspect_response(id: &str, name: &str) -> ContainerInspectResponse {
ContainerInspectResponse {
id: Some(id.to_string()),
name: Some(format!("/{}", name)),
config: Some(ContainerConfig {
labels: Some(HashMap::from([(
"harborshield.enabled".to_string(),
"true".to_string(),
)])),
exposed_ports: Some(HashMap::from([("80/tcp".to_string(), HashMap::new())])),
..Default::default()
}),
network_settings: Some(NetworkSettings {
networks: Some(HashMap::from([(
"bridge".to_string(),
EndpointSettings {
ip_address: Some("172.17.0.2".to_string()),
..Default::default()
},
)])),
..Default::default()
}),
host_config: Some(HostConfig {
port_bindings: Some(HashMap::from([(
"80/tcp".to_string(),
Some(vec![PortBinding {
host_port: Some("8080".to_string()),
..Default::default()
}]),
)])),
network_mode: None,
..Default::default()
}),
state: Some(ContainerState {
running: Some(true),
..Default::default()
}),
..Default::default()
}
}
#[test]
fn test_container_info_from_inspect() {
let inspect = create_test_inspect_response("test123", "test-container");
let info = Container::from_inspect(inspect).unwrap();
assert_eq!(info.id, "test123");
assert_eq!(info.name, "test-container");
assert_eq!(
info.labels.get("harborshield.enabled"),
Some(&"true".to_string())
);
assert_eq!(info.networks.len(), 1);
let bridge_network = info.networks.get("bridge").unwrap();
assert_eq!(bridge_network.name, "bridge");
assert_eq!(
bridge_network.ip_addresses.get(0),
Some(&IpAddr::V4(Ipv4Addr::new(172, 17, 0, 2)))
);
assert_eq!(info.ports.len(), 1);
assert_eq!(info.ports[0].container_port, 80);
assert_eq!(info.ports[0].host_port, Some(8080));
assert_eq!(info.ports[0].protocol, "tcp");
assert!(!info.uses_host_network);
}
#[test]
fn test_container_info_missing_id() {
let mut inspect = create_test_inspect_response("test", "test");
inspect.id = None;
let result = Container::from_inspect(inspect);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("missing ID"));
}
#[test]
fn test_container_info_missing_name() {
let mut inspect = create_test_inspect_response("test", "test");
inspect.name = None;
let result = Container::from_inspect(inspect);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("missing name"));
}
#[test]
fn test_container_info_no_config() {
let mut inspect = create_test_inspect_response("test", "test");
inspect.config = None;
let info = Container::from_inspect(inspect).unwrap();
assert!(info.labels.is_empty());
assert!(info.ports.is_empty());
}
#[test]
fn test_container_info_no_networks() {
let mut inspect = create_test_inspect_response("test", "test");
inspect.network_settings = None;
let info = Container::from_inspect(inspect).unwrap();
assert!(info.networks.is_empty());
}
#[test]
fn test_container_info_multiple_networks() {
let mut inspect = create_test_inspect_response("test", "test");
if let Some(ref mut network_settings) = inspect.network_settings {
network_settings.networks = Some(HashMap::from([
(
"bridge".to_string(),
EndpointSettings {
ip_address: Some("172.17.0.2".to_string()),
..Default::default()
},
),
(
"custom".to_string(),
EndpointSettings {
ip_address: Some("10.0.0.2".to_string()),
..Default::default()
},
),
]));
}
let info = Container::from_inspect(inspect).unwrap();
assert_eq!(info.networks.len(), 2);
}
#[test]
fn test_container_info_invalid_ip() {
let mut inspect = create_test_inspect_response("test", "test");
if let Some(ref mut network_settings) = inspect.network_settings {
network_settings.networks = Some(HashMap::from([(
"bridge".to_string(),
EndpointSettings {
ip_address: Some("invalid-ip".to_string()),
..Default::default()
},
)]));
}
let info = Container::from_inspect(inspect).unwrap();
assert_eq!(info.networks.len(), 1);
let bridge_network = info.networks.get("bridge").unwrap();
assert!(bridge_network.ip_addresses.is_empty());
}
#[test]
fn test_container_info_multiple_ports() {
let mut inspect = create_test_inspect_response("test", "test");
if let Some(ref mut config) = inspect.config {
config.exposed_ports = Some(HashMap::from([
("80/tcp".to_string(), HashMap::new()),
("443/tcp".to_string(), HashMap::new()),
("8080/udp".to_string(), HashMap::new()),
]));
}
let info = Container::from_inspect(inspect).unwrap();
assert_eq!(info.ports.len(), 3);
let mut ports: Vec<_> = info.ports.iter().map(|p| p.container_port).collect();
ports.sort();
assert_eq!(ports, vec![80, 443, 8080]);
}
#[test]
fn test_container_info_strip_leading_slash() {
let inspect = create_test_inspect_response("test", "test");
let info = Container::from_inspect(inspect).unwrap();
assert_eq!(info.name, "test");
assert!(!info.name.starts_with('/'));
}
#[test]
fn test_container_info_complex_labels() {
let mut inspect = create_test_inspect_response("test", "test");
if let Some(ref mut config) = inspect.config {
config.labels = Some(HashMap::from([
("harborshield.enabled".to_string(), "true".to_string()),
(
"harborshield.rules".to_string(),
"complex yaml config".to_string(),
),
("com.example.version".to_string(), "1.0.0".to_string()),
("maintainer".to_string(), "test@example.com".to_string()),
]));
}
let info = Container::from_inspect(inspect).unwrap();
assert_eq!(info.labels.len(), 4);
assert_eq!(
info.labels.get("harborshield.enabled"),
Some(&"true".to_string())
);
assert_eq!(
info.labels.get("harborshield.rules"),
Some(&"complex yaml config".to_string())
);
}
#[test]
fn test_container_info_compose_aliases() {
let mut inspect = create_test_inspect_response("abcdef123456", "myproject_web_1");
if let Some(ref mut config) = inspect.config {
config.labels = Some(HashMap::from([
(
"com.docker.compose.project".to_string(),
"myproject".to_string(),
),
("com.docker.compose.service".to_string(), "web".to_string()),
(
"harborshield.aliases".to_string(),
"frontend,api".to_string(),
),
]));
}
let info = Container::from_inspect(inspect).unwrap();
let expected_aliases = vec![
"web".to_string(),
"web.myproject".to_string(),
"myproject_web".to_string(),
"abcdef123456".to_string(), "frontend".to_string(),
"api".to_string(),
];
for alias in expected_aliases {
assert!(info.aliases.contains(&alias), "Missing alias: {}", alias);
}
}
#[test]
fn test_container_info_network_aliases() {
let mut inspect = create_test_inspect_response("test", "test");
if let Some(ref mut network_settings) = inspect.network_settings {
network_settings.networks = Some(HashMap::from([
(
"bridge".to_string(),
EndpointSettings {
ip_address: Some("172.17.0.2".to_string()),
aliases: Some(vec!["web".to_string(), "frontend".to_string()]),
..Default::default()
},
),
(
"custom".to_string(),
EndpointSettings {
ip_address: Some("10.0.0.2".to_string()),
aliases: Some(vec!["api".to_string()]),
..Default::default()
},
),
]));
}
let info = Container::from_inspect(inspect).unwrap();
let bridge_network = info.networks.get("bridge").unwrap();
assert!(bridge_network.aliases.contains(&"web".to_string()));
assert!(bridge_network.aliases.contains(&"frontend".to_string()));
let custom_network = info.networks.get("custom").unwrap();
assert!(custom_network.aliases.contains(&"api".to_string()));
assert!(info.aliases.contains(&"web".to_string()));
assert!(info.aliases.contains(&"frontend".to_string()));
assert!(info.aliases.contains(&"api".to_string()));
}
#[test]
fn test_docker_env_parsing() {
init_test();
let test_cases = vec![
("unix:///var/run/docker.sock", true, "unix socket"),
("/var/run/docker.sock", true, "unix socket path"),
("tcp://localhost:2375", false, "tcp without TLS"),
("tcp://remote:2376", true, "tcp with TLS check"),
("http://localhost:2375", false, "http URL"),
("https://remote:2376", true, "https URL"),
];
for (docker_host, _needs_tls_check, _description) in test_cases {
with_vars(vec![("DOCKER_HOST", Some(docker_host))], || {
let result = std::panic::catch_unwind(|| DockerClient::builder().build());
if result.is_err() {
eprintln!("Skipping test case due to crypto provider issue");
return;
}
let _ = result.unwrap();
});
}
}
#[test]
fn test_docker_tls_env_vars() {
init_test();
use std::fs;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let cert_path = temp_dir.path();
fs::write(cert_path.join("ca.pem"), "fake ca cert").unwrap();
fs::write(cert_path.join("cert.pem"), "fake client cert").unwrap();
fs::write(cert_path.join("key.pem"), "fake client key").unwrap();
with_vars(
vec![
("DOCKER_HOST", Some("tcp://remote:2376")),
("DOCKER_TLS_VERIFY", Some("1")),
("DOCKER_CERT_PATH", Some(cert_path.to_str().unwrap())),
],
|| {
let result = std::panic::catch_unwind(|| DockerClient::builder().build());
if result.is_err() {
eprintln!("Skipping TLS test due to crypto provider issue");
return;
}
},
);
with_vars(
vec![
("DOCKER_HOST", Some("tcp://remote:2376")),
("DOCKER_TLS_VERIFY", Some("true")),
("DOCKER_CERT_PATH", Some(cert_path.to_str().unwrap())),
],
|| {
let _ = std::panic::catch_unwind(|| DockerClient::builder().build());
},
);
}
#[test]
fn test_docker_api_version_warning() {
init_test();
with_vars(vec![("DOCKER_API_VERSION", Some("1.40"))], || {
let result = DockerClient::builder().build();
if let Ok(client) = result {
assert_eq!(client.api_version(), Some("v1.40"));
}
});
}
#[test]
fn test_docker_api_version_unsupported() {
init_test();
with_vars(vec![("DOCKER_API_VERSION", Some("1.99"))], || {
let result = DockerClient::builder().build();
let _ = result; });
}
#[test]
fn test_docker_api_version_negotiation() {
init_test();
let result = DockerClient::builder().build();
if let Ok(client) = result {
let _ = client.api_version();
}
}
#[test]
fn test_docker_cert_path_default() {
init_test();
with_vars(
vec![
("DOCKER_HOST", Some("tcp://remote:2376")),
("DOCKER_TLS_VERIFY", Some("1")),
("HOME", Some("/tmp/test_home")),
],
|| {
let result = std::panic::catch_unwind(|| DockerClient::builder().build());
if result.is_err() {
eprintln!("Skipping test due to crypto provider issue");
return;
}
},
);
}
#[test]
fn test_container_info_host_network() {
let mut inspect = create_test_inspect_response("test123", "test-container");
if let Some(ref mut host_config) = inspect.host_config {
host_config.network_mode = Some("host".to_string());
}
let info = Container::from_inspect(inspect).unwrap();
assert!(info.uses_host_network);
}
#[test]
fn test_container_info_bridge_network() {
let mut inspect = create_test_inspect_response("test123", "test-container");
if let Some(ref mut host_config) = inspect.host_config {
host_config.network_mode = Some("bridge".to_string());
}
let info = Container::from_inspect(inspect).unwrap();
assert!(!info.uses_host_network);
}
#[test]
fn test_container_info_no_network_mode() {
let inspect = create_test_inspect_response("test123", "test-container");
let info = Container::from_inspect(inspect).unwrap();
assert!(!info.uses_host_network);
}
#[tokio::test]
async fn test_check_api_endpoint() {
init_test();
let result = DockerClient::builder().build();
if let Ok(client) = result {
let endpoints = vec![
("/containers/json", true),
("/images/json", true),
("/secrets", true), ("/unknown/endpoint", false),
];
for (endpoint, _expected) in endpoints {
let _ = client.check_api_endpoint(endpoint).await;
}
}
}
#[test]
fn test_check_api_endpoint_by_version() {
init_test();
let result = DockerClient::builder().build();
if let Ok(client) = result {
assert!(client.check_api_endpoint_by_version("/containers/json"));
assert!(client.check_api_endpoint_by_version("/images/json"));
assert!(client.check_api_endpoint_by_version("/unknown/endpoint"));
}
}
#[tokio::test]
#[ignore = "Requires Docker daemon"]
async fn test_docker_version_negotiation() {
init_test();
async_with_vars(vec![("DOCKER_API_VERSION", None::<&str>)], async {
let result = DockerClient::builder().build();
assert!(result.is_ok() || result.is_err());
if let Ok(client) = result {
let negotiated_result = client.negotiate_version().await;
assert!(negotiated_result.is_ok());
if let Ok(negotiated_client) = negotiated_result {
let _ = negotiated_client.ping().await;
}
}
})
.await;
}
#[tokio::test]
async fn test_docker_version_override_with_negotiation() {
init_test();
async_with_vars(vec![("DOCKER_API_VERSION", Some("1.42"))], async {
let result = DockerClient::builder().build();
if let Ok(client) = result {
assert_eq!(client.api_version(), Some("v1.42"));
}
})
.await;
}
}