Single-commit clean baseline after security scrub of niche-tells, project codenames, internal jargon, and contributor-email leaks. Contents: - 100 Rust crates (_primitives/_rust/) - 37 agent manifests (_manifests/) + generated specs (_generated/) - 67 user-invocable skills (skills/) - 33 hooks (hooks/) - Composition blocks (_blocks/) - Documentation (docs/, README.md) - TS adapter packages (_ts_packages/) - Assembler (_assembler/) - Roles (_roles/) - Templates (_templates/) - Forgejo CI (.forgejo/) Author: Denis Parfionovich <info@greendragon.info> License: see LICENSE.
120 lines
4.3 KiB
Rust
120 lines
4.3 KiB
Rust
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright 2026 <author org>
|
|
//
|
|
//! Smoke tests for [`kei_net_ipsec::IpsecMode`].
|
|
//!
|
|
//! All shell-out is routed through [`kei_net_ipsec::MockRunner`]; nothing
|
|
//! invokes a real `swanctl` binary, so these tests run without root and
|
|
//! without strongSwan installed.
|
|
|
|
use kei_net_ipsec::{parse_sas_output, IpsecMode, MockRunner, RunOutput};
|
|
use kei_runtime_core::traits::network::{NetworkConfig, NetworkMode};
|
|
use kei_runtime_core::HasDna;
|
|
use std::sync::Arc;
|
|
|
|
fn empty_cfg() -> NetworkConfig {
|
|
NetworkConfig {
|
|
mode: "ipsec".into(),
|
|
hostname: "test.example".into(),
|
|
auth_secret_env: None,
|
|
allowed_ports: vec![500, 4500],
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn configure_invokes_swanctl_initiate() {
|
|
let mock = MockRunner::new()
|
|
.with_ok("swanctl_--load-all", "")
|
|
.with_ok("swanctl_--initiate_--child_home", "");
|
|
let mock = Arc::new(mock);
|
|
let mode = IpsecMode::new(mock.clone(), None, "home").expect("ctor");
|
|
mode.configure(&empty_cfg()).await.expect("configure ok");
|
|
|
|
let calls = mock.recorded();
|
|
assert!(
|
|
calls.iter().any(|(c, a)| c == "swanctl" && a == &["--load-all".to_string()]),
|
|
"must invoke swanctl --load-all"
|
|
);
|
|
assert!(
|
|
calls.iter().any(|(c, a)| c == "swanctl"
|
|
&& a == &["--initiate".to_string(), "--child".to_string(), "home".to_string()]),
|
|
"must invoke swanctl --initiate --child <name>"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn teardown_invokes_swanctl_terminate() {
|
|
let mock = MockRunner::new().with_ok("swanctl_--terminate_--child_home", "");
|
|
let mock = Arc::new(mock);
|
|
let mode = IpsecMode::new(mock.clone(), None, "home").expect("ctor");
|
|
mode.teardown().await.expect("teardown ok");
|
|
|
|
let calls = mock.recorded();
|
|
assert_eq!(calls.len(), 1);
|
|
assert_eq!(calls[0].0, "swanctl");
|
|
assert_eq!(
|
|
calls[0].1,
|
|
vec!["--terminate".to_string(), "--child".to_string(), "home".to_string()]
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn peers_parses_canned_sas_output() {
|
|
let canned = "\
|
|
home: #1, ESTABLISHED, IKEv2, abcd_i* def_r
|
|
local 'gw' @ 192.0.2.1[500]
|
|
remote 'peer' @ 198.51.100.7[4500]
|
|
encr_alg=AES_GCM_16
|
|
home: #2, reqid 1, INSTALLED, TUNNEL, ESP:AES_GCM_16
|
|
bytes_i (1.04K, 1067 bytes), packets_i (12 packets)
|
|
bytes_o (3.50K, 3584 bytes), packets_o (24 packets)
|
|
";
|
|
let mock = MockRunner::new().with_ok("swanctl_--list-sas", canned);
|
|
let mode = IpsecMode::new(Arc::new(mock), None, "home").expect("ctor");
|
|
let peers = mode.peers().await.expect("peers ok");
|
|
assert_eq!(peers.len(), 1);
|
|
assert_eq!(peers[0].addr, "198.51.100.7");
|
|
assert_eq!(peers[0].bytes_rx, 1067);
|
|
assert_eq!(peers[0].bytes_tx, 3584);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn dna_has_ip_cap() {
|
|
let mock = MockRunner::new();
|
|
let mode = IpsecMode::new(Arc::new(mock), None, "home").expect("ctor");
|
|
let caps = mode.dna().caps();
|
|
assert!(caps.contains("IP"), "DNA caps must contain IP token, got {caps:?}");
|
|
assert!(caps.contains("PR"), "DNA caps must contain PR token");
|
|
assert!(caps.contains("AP"), "DNA caps must contain AP token");
|
|
assert_eq!(mode.mode_name(), "ipsec");
|
|
assert!(mode.is_public(), "ipsec is the public-IP NetworkMode");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn parse_handles_empty_input() {
|
|
// Direct parser invariant.
|
|
assert!(parse_sas_output("").is_empty());
|
|
|
|
// End-to-end: empty `swanctl --list-sas` stdout must yield `Ok(vec![])`.
|
|
let mock = MockRunner::new().with_ok("swanctl_--list-sas", "");
|
|
let mode = IpsecMode::new(Arc::new(mock), None, "home").expect("ctor");
|
|
let peers = mode.peers().await.expect("peers ok");
|
|
assert!(peers.is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn nonzero_exit_surfaces_swanctl_failed() {
|
|
// Defensive: `swanctl` returning non-zero exit must NOT be silently
|
|
// swallowed — it must propagate as Network error.
|
|
let mock = MockRunner::new().with_run(
|
|
"swanctl_--terminate_--child_home",
|
|
RunOutput::fail(1, "child SA 'home' not found"),
|
|
);
|
|
let mode = IpsecMode::new(Arc::new(mock), None, "home").expect("ctor");
|
|
let err = mode.teardown().await.expect_err("must fail");
|
|
let msg = format!("{err}");
|
|
assert!(
|
|
msg.contains("network") || msg.contains("swanctl") || msg.contains("not found"),
|
|
"error must surface swanctl failure context, got: {msg}"
|
|
);
|
|
}
|