use super::*;
use crate::compose::types::{ComposeFile, NetworkConfig, Service};
fn empty_file() -> ComposeFile {
ComposeFile::default()
}
fn file_with_named_network(key: &str, name: &str) -> ComposeFile {
let cfg = NetworkConfig {
name: Some(name.to_string()),
..Default::default()
};
let mut file = empty_file();
file.networks.insert(key.to_string(), Some(cfg));
file
}
#[test]
fn resolve_network_name_key_not_found_prefixes_project() {
let file = empty_file();
assert_eq!(resolve_network_name("mynet", &file, "proj"), "proj_mynet");
}
#[test]
fn resolve_network_name_uses_config_name_over_prefix() {
let file = file_with_named_network("mynet", "custom-net-name");
assert_eq!(
resolve_network_name("mynet", &file, "proj"),
"custom-net-name"
);
}
#[test]
fn resolve_network_name_external_uses_key_not_prefix() {
let cfg = NetworkConfig {
external: Some(true),
..Default::default()
};
let mut file = empty_file();
file.networks.insert("shared".to_string(), Some(cfg));
assert_eq!(resolve_network_name("shared", &file, "proj"), "shared");
}
#[test]
fn resolve_network_mode_explicit_mode() {
let svc = Service {
network_mode: Some("host".to_string()),
..Default::default()
};
let file = empty_file();
let (ns, nets) = resolve_network_mode("web", &svc, &file, "proj");
assert!(ns.is_some());
assert_eq!(ns.unwrap().nsmode, "host");
assert!(nets.is_empty());
}
fn file_with_service(svc_name: &str, svc: Service) -> ComposeFile {
let mut file = empty_file();
file.services.insert(svc_name.to_string(), svc);
file
}
#[test]
fn network_mode_service_single_replica_uses_base_name() {
let target = Service::default();
let file = file_with_service("db", target);
let svc = Service {
network_mode: Some("service:db".to_string()),
..Default::default()
};
let (ns, _) = resolve_network_mode("web", &svc, &file, "proj");
let ns = ns.unwrap();
assert_eq!(ns.nsmode, "container");
assert_eq!(ns.value.as_deref(), Some("proj-db"));
}
#[test]
fn network_mode_service_scaled_replicas_resolves_replica_one() {
let target = Service {
scale: Some(3),
..Default::default()
};
let file = file_with_service("db", target);
let svc = Service {
network_mode: Some("service:db".to_string()),
..Default::default()
};
let (ns, _) = resolve_network_mode("web", &svc, &file, "proj");
assert_eq!(ns.unwrap().value.as_deref(), Some("proj-db-1"));
}
#[test]
fn network_mode_service_deploy_replicas_resolves_replica_one() {
use crate::compose::types::DeployConfig;
let target = Service {
deploy: Some(DeployConfig {
replicas: Some(2),
..Default::default()
}),
..Default::default()
};
let file = file_with_service("db", target);
let svc = Service {
network_mode: Some("service:db".to_string()),
..Default::default()
};
let (ns, _) = resolve_network_mode("web", &svc, &file, "proj");
assert_eq!(ns.unwrap().value.as_deref(), Some("proj-db-1"));
}
#[test]
fn network_mode_service_container_name_wins_over_replica() {
let target = Service {
scale: Some(4),
container_name: Some("custom-db".to_string()),
..Default::default()
};
let file = file_with_service("db", target);
let svc = Service {
network_mode: Some("service:db".to_string()),
..Default::default()
};
let (ns, _) = resolve_network_mode("web", &svc, &file, "proj");
assert_eq!(ns.unwrap().value.as_deref(), Some("custom-db"));
}
#[test]
fn network_mode_service_unknown_target_uses_raw_name() {
let file = empty_file();
let svc = Service {
network_mode: Some("service:missing".to_string()),
..Default::default()
};
let (ns, _) = resolve_network_mode("web", &svc, &file, "proj");
assert_eq!(ns.unwrap().value.as_deref(), Some("missing"));
}
#[test]
fn resolve_network_mode_no_networks() {
let svc = Service::default();
let file = empty_file();
let (ns, nets) = resolve_network_mode("web", &svc, &file, "proj");
assert!(ns.is_none());
assert!(nets.is_empty());
}
#[test]
fn build_per_network_options_seeds_service_name_alias() {
let opts = build_per_network_options("web", None, None);
assert_eq!(opts.aliases, vec!["web".to_string()]);
assert!(opts.static_ips.is_empty());
}
#[test]
fn build_per_network_options_empty_service_name_adds_no_alias() {
let opts = build_per_network_options("", None, None);
assert!(opts.aliases.is_empty());
}
#[test]
fn build_per_network_options_with_aliases() {
use crate::compose::types::ServiceNetworkConfig;
let cfg = ServiceNetworkConfig {
aliases: Some(vec!["api".to_string()]),
..Default::default()
};
let opts = build_per_network_options("web", Some(&cfg), None);
assert_eq!(opts.aliases, vec!["web".to_string(), "api".to_string()]);
}
#[test]
fn build_per_network_options_does_not_duplicate_service_name() {
use crate::compose::types::ServiceNetworkConfig;
let cfg = ServiceNetworkConfig {
aliases: Some(vec!["web".to_string(), "api".to_string()]),
..Default::default()
};
let opts = build_per_network_options("web", Some(&cfg), None);
assert_eq!(opts.aliases, vec!["web".to_string(), "api".to_string()]);
}
#[test]
fn build_per_network_options_with_ipv4() {
use crate::compose::types::ServiceNetworkConfig;
let cfg = ServiceNetworkConfig {
ipv4_address: Some("10.0.0.5".to_string()),
..Default::default()
};
let opts = build_per_network_options("web", Some(&cfg), None);
assert!(opts.static_ips.contains(&"10.0.0.5".to_string()));
}
#[test]
fn fallback_mac_applied_when_no_config() {
let opts = build_per_network_options("web", None, Some("02:42:ac:11:00:02"));
assert_eq!(opts.static_mac.as_deref(), Some("02:42:ac:11:00:02"));
}
#[test]
fn lease_range_ipv4_reserves_network_and_broadcast() {
let lr = lease_range_from_cidr("172.28.5.0/24").unwrap();
assert_eq!(lr.start_ip.as_deref(), Some("172.28.5.1"));
assert_eq!(lr.end_ip.as_deref(), Some("172.28.5.254"));
}
#[test]
fn lease_range_ipv4_slash31_uses_both_addresses() {
let lr = lease_range_from_cidr("10.0.0.0/31").unwrap();
assert_eq!(lr.start_ip.as_deref(), Some("10.0.0.0"));
assert_eq!(lr.end_ip.as_deref(), Some("10.0.0.1"));
}
#[test]
fn lease_range_ipv6_full_span() {
let lr = lease_range_from_cidr("2001:db8::/120").unwrap();
assert_eq!(lr.start_ip.as_deref(), Some("2001:db8::"));
assert_eq!(lr.end_ip.as_deref(), Some("2001:db8::ff"));
}
#[test]
fn lease_range_invalid_cidr_is_none() {
assert!(lease_range_from_cidr("not-a-cidr").is_none());
assert!(lease_range_from_cidr("10.0.0.0/40").is_none());
}
#[test]
fn ipam_options_include_driver_and_options() {
use crate::compose::types::IpamConfig;
let ipam = IpamConfig {
driver: Some("host-local".into()),
options: [("foo".to_string(), "bar".to_string())].into(),
..Default::default()
};
let opts = build_ipam_options(&ipam);
assert_eq!(opts.get("driver").map(String::as_str), Some("host-local"));
assert_eq!(opts.get("foo").map(String::as_str), Some("bar"));
}
#[test]
fn per_network_interface_name_forwarded() {
use crate::compose::types::ServiceNetworkConfig;
let cfg = ServiceNetworkConfig {
interface_name: Some("eth1".into()),
..Default::default()
};
let opts = build_per_network_options("web", Some(&cfg), None);
assert_eq!(opts.interface_name.as_deref(), Some("eth1"));
}
#[test]
fn per_network_mac_takes_precedence_over_fallback() {
use crate::compose::types::ServiceNetworkConfig;
let cfg = ServiceNetworkConfig {
mac_address: Some("aa:bb:cc:dd:ee:ff".to_string()),
..Default::default()
};
let opts = build_per_network_options("web", Some(&cfg), Some("02:42:ac:11:00:03"));
assert_eq!(opts.static_mac.as_deref(), Some("aa:bb:cc:dd:ee:ff"));
}