//! Integration tests for [`aura_cli::init::server_init`]. //! //! Drives the in-process helper directly (no clap parsing, no binary spawn) and asserts that the //! generated CA + server cert + server.toml exist on disk and parse cleanly. Each switch on the //! `ServerInitOpts` flips the corresponding section in the rendered TOML. use std::path::PathBuf; use aura_cli::config::ServerConfigFile; use aura_cli::init::{self, ServerInitOpts}; /// Unique temp dir for one test (no `tempfile` dependency in the workspace). fn temp_dir(tag: &str) -> PathBuf { let mut dir = std::env::temp_dir(); dir.push(format!( "aura-cli-server-init-{tag}-{}-{}", std::process::id(), std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos() )); std::fs::create_dir_all(&dir).expect("create temp dir"); dir } /// Build a baseline options struct with the temp directories pre-filled. Per-test mutations layer /// on top of this. fn base_opts(tag: &str) -> (ServerInitOpts, PathBuf) { let root = temp_dir(tag); let pki = root.join("pki"); let cfg = root.join("server.toml"); let mut opts = ServerInitOpts::new("vpn.example.com", &pki); opts.out_config = cfg.clone(); // Force the no_nat path by default — the integration test runner may or may not have a // detectable default route, so the per-test `egress_iface` / `no_nat` overrides are explicit. opts.no_nat = true; (opts, root) } /// Happy path: CA, server cert and server.toml all written and the TOML parses back. #[test] fn server_init_writes_and_parses() { let (opts, root) = base_opts("happy"); let report = init::server_init(&opts).expect("server-init succeeds"); assert!(report.ca_cert.exists(), "ca.crt exists"); assert!(report.ca_key.exists(), "ca.key exists"); assert!(report.server_cert.exists(), "server.crt exists"); assert!(report.server_key.exists(), "server.key exists"); assert!(report.server_config.exists(), "server.toml exists"); let cfg = ServerConfigFile::load(&report.server_config).expect("server.toml parses"); // v3.4: server-init defaults moved off 443/444 to 8443/8444 to dodge sing-box / Hysteria2 // collisions; the listen-address derives from udp_port. assert_eq!(cfg.server.listen, "0.0.0.0:8443"); assert_eq!(cfg.tunnel.pool_cidr, "10.7.0.0/24"); assert_eq!(cfg.transport.udp_port, 8443); assert_eq!(cfg.transport.quic_port, 8444); // no-nat was set in the baseline. assert!(cfg.server.nat.is_none(), "no [server.nat] section"); // knock / cover default to disabled. assert!(!cfg.transport.knock.enabled); assert!(!cfg.transport.cover.enabled); // PKI section points at the generated files. assert_eq!(cfg.pki.ca_cert, report.ca_cert.to_string_lossy()); // Cleanup is best-effort. let _ = std::fs::remove_dir_all(&root); } /// `--enable-knock` and `--enable-cover-traffic` flip the [transport.*] sections on. #[test] fn server_init_enables_anti_surveillance() { let (mut opts, root) = base_opts("knock"); opts.enable_knock = true; opts.enable_cover_traffic = true; let report = init::server_init(&opts).expect("server-init succeeds"); let cfg = ServerConfigFile::load(&report.server_config).expect("parse"); assert!(cfg.transport.knock.enabled, "knock enabled"); assert_eq!(cfg.transport.knock.knock_secret_source, "ca_fingerprint"); assert!(cfg.transport.cover.enabled, "cover enabled"); assert_eq!(cfg.transport.cover.mean_interval_ms, 500); let _ = std::fs::remove_dir_all(&root); } /// `egress_iface = "eth0"` + `no_nat = false` writes a `[server.nat]` section. #[test] fn server_init_writes_nat_when_egress_explicit() { let (mut opts, root) = base_opts("nat"); opts.no_nat = false; opts.egress_iface = Some("eth0".to_string()); let report = init::server_init(&opts).expect("server-init succeeds"); let cfg = ServerConfigFile::load(&report.server_config).expect("parse"); let nat = cfg.server.nat.expect("[server.nat] present"); assert!(nat.auto, "nat.auto = true"); assert_eq!(nat.egress_iface, "eth0"); let _ = std::fs::remove_dir_all(&root); } /// `run_as = "nobody"` ends up in `[server] run_as` and `no_logs` toggles parse cleanly. #[test] fn server_init_run_as_and_no_logs_present() { let (mut opts, root) = base_opts("runas"); opts.run_as = Some("nobody".to_string()); let report = init::server_init(&opts).expect("server-init succeeds"); let cfg = ServerConfigFile::load(&report.server_config).expect("parse"); assert_eq!(cfg.server.run_as.as_deref(), Some("nobody")); // `no_logs` is emitted with the default `false`. assert!(!cfg.server.no_logs); let _ = std::fs::remove_dir_all(&root); } /// Without `--force`, re-running over an existing CA errors out cleanly. #[test] fn server_init_refuses_to_clobber_without_force() { let (opts, root) = base_opts("clobber"); init::server_init(&opts).expect("first run succeeds"); // Re-run should fail because the CA already exists. let err = init::server_init(&opts).unwrap_err().to_string(); assert!( err.contains("CA already exists") || err.contains("already exists"), "expected overwrite refusal, got: {err}" ); // With force the second run succeeds. let mut forced = opts.clone(); forced.force = true; let report = init::server_init(&forced).expect("--force overwrites"); assert!(report.ca_cert.exists()); let _ = std::fs::remove_dir_all(&root); }