Files
xah30 9462558a15 test(cli): use ..Default::default() in tests/os_routes.rs SplitRoutes literals
Companion to v3.5 — the new force_vpn_cidrs field broke the two integration
tests that used positional SplitRoutes literals instead of struct-update
syntax. Switched both to .. Default::default() so the test sites are
forward-compatible with any further SplitRoutes additions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 22:37:53 +03:00

178 lines
5.8 KiB
Rust

//! Integration tests for the OS-level split-tunnel helper (`aura_cli::os_routes::OsRouteGuard`).
//!
//! These tests only exercise the dry-run path: real `ip` / `route` programming needs root and a
//! live network stack, which is inappropriate for the unit test runner. The dry-run path is
//! platform-portable: it logs `would run: ...` for both the Linux and macOS plans (plus the
//! Windows-stub notice) and never touches the host.
use std::net::IpAddr;
use aura_cli::config::{ClientConfigFile, OsRoutesSection, SplitRule, SplitSection};
use aura_cli::os_routes::{DefaultAction, OsRouteGuard, SplitRoutes};
/// Dry-run install must succeed on every host (Linux, macOS, Windows) regardless of which
/// gateway / egress hints are provided. Drop must not panic.
#[test]
fn dry_run_install_succeeds_on_any_platform() {
let split = SplitRoutes {
default: DefaultAction::Vpn,
direct_cidrs: vec!["192.168.0.0/16".parse().unwrap()],
direct_hosts: vec!["1.2.3.4".parse().unwrap()],
..Default::default()
};
let guard = OsRouteGuard::install("aura0", &split, None, None, true)
.expect("dry_run install must succeed everywhere");
drop(guard);
}
/// Dry-run also accepts explicit gateway / egress overrides — they are rendered into the
/// `would run: ...` lines without needing to invoke the host's `ip`/`route` binary.
#[test]
fn dry_run_install_accepts_explicit_overrides() {
let split = SplitRoutes {
default: DefaultAction::Direct,
vpn_cidrs: vec!["10.7.0.0/24".parse().unwrap()],
..Default::default()
};
let guard = OsRouteGuard::install(
"utun4",
&split,
Some("10.0.0.1"),
Some("en0"),
/* dry_run */ true,
)
.expect("dry_run install with explicit gateway/egress must succeed");
drop(guard);
}
/// `SplitRoutes::from_config` collects CIDRs from both branches and any resolved domain hosts.
#[test]
fn split_routes_from_config_collects_everything() {
let split = SplitSection {
default: "VPN".into(),
direct: vec![SplitRule {
cidr: Some("192.168.0.0/16".into()),
domain: None,
}],
vpn: vec![SplitRule {
cidr: Some("10.7.0.0/24".into()),
domain: None,
}],
};
let resolved: Vec<(String, aura_tunnel::RouteAction, Vec<IpAddr>)> = vec![(
"intranet.example.com".into(),
aura_tunnel::RouteAction::Direct,
vec!["1.2.3.4".parse().unwrap()],
)];
let r = SplitRoutes::from_config(&split, &resolved);
assert_eq!(r.default, DefaultAction::Vpn);
assert_eq!(r.direct_cidrs.len(), 1);
assert_eq!(r.vpn_cidrs.len(), 1);
assert_eq!(r.direct_hosts.len(), 1);
assert!(r.vpn_hosts.is_empty());
}
/// A `client.toml` without a `[tunnel.os_routes]` section still parses; the field is `None`.
/// This is the explicit back-compat check — old configs do not need to know about the new
/// section.
#[test]
fn client_toml_without_os_routes_section_parses() {
let minimal = r#"
[client]
name = "x"
server_addr = "1.2.3.4:443"
sni = "a"
[pki]
ca_cert = "a"
cert = "b"
key = "c"
[tunnel]
local_ip = "10.7.0.2"
[tunnel.split]
default = "VPN"
"#;
let cfg = ClientConfigFile::parse(minimal).expect("parses minimal client.toml");
assert!(
cfg.tunnel.os_routes.is_none(),
"without the section, os_routes is None — runtime falls back to enabled = true default"
);
}
/// `[tunnel.os_routes]` with `enabled = true, dry_run = true` parses end-to-end and exposes the
/// flags to the client startup path.
#[test]
fn client_toml_parses_os_routes_section() {
let s = r#"
[client]
name = "x"
server_addr = "1.2.3.4:443"
sni = "a"
[pki]
ca_cert = "a"
cert = "b"
key = "c"
[tunnel]
local_ip = "10.7.0.2"
[tunnel.os_routes]
enabled = true
dry_run = true
gateway = "192.168.1.1"
egress_iface = "en0"
[tunnel.split]
default = "VPN"
"#;
let cfg = ClientConfigFile::parse(s).expect("parses client.toml with [tunnel.os_routes]");
let os = cfg.tunnel.os_routes.expect("section present");
assert!(os.enabled);
assert!(os.dry_run);
assert_eq!(os.gateway.as_deref(), Some("192.168.1.1"));
assert_eq!(os.egress_iface.as_deref(), Some("en0"));
}
/// `OsRoutesSection::default()` matches the documented v2 semantics: enabled by default with
/// no dry_run and no explicit gateway/egress (auto-detected at runtime).
#[test]
fn os_routes_section_default_values() {
let d = OsRoutesSection::default();
assert!(
d.enabled,
"default is enabled = true (v2 eliminates the v1 stub)"
);
assert!(!d.dry_run);
assert!(d.gateway.is_none());
assert!(d.egress_iface.is_none());
}
/// v3.3: a Windows-style client.toml (with the operator's pre-detected gateway already pinned
/// in `[tunnel.os_routes]`) still parses and the dry-run install renders the windows plan in
/// the logs. We do not assert on the log contents here — that is covered by the inner
/// `windows_plan_default_vpn` unit test in `os_routes.rs` — but we *do* verify that the API
/// surface accepts the same hints on every host (no Windows-only fields).
#[test]
fn dry_run_install_windows_style_overrides_succeed_anywhere() {
let split = SplitRoutes {
default: DefaultAction::Vpn,
direct_cidrs: vec!["192.168.0.0/16".parse().unwrap()],
direct_hosts: vec!["1.2.3.4".parse().unwrap()],
..Default::default()
};
// On Windows the "egress" hint is the upstream interface IP, not its display name.
// The dry-run path renders this verbatim into the windows plan.
let guard = OsRouteGuard::install(
"Aura",
&split,
Some("192.168.1.1"),
Some("192.168.1.42"),
/* dry_run */ true,
)
.expect("dry_run with Windows-style overrides must succeed on every host");
drop(guard);
}