feat: Add unit tests for CLI and MasterConnector, including certificate extraction and validation
This commit is contained in:
@@ -79,3 +79,84 @@ pub enum Commands {
|
||||
ca_cert: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::Parser;
|
||||
|
||||
use super::{Cli, Commands};
|
||||
|
||||
#[test]
|
||||
fn parses_serve_flag_without_subcommand() {
|
||||
let parsed = Cli::try_parse_from(["nxmesh-agent", "--serve"]);
|
||||
assert!(parsed.is_ok());
|
||||
|
||||
let parsed = parsed.ok();
|
||||
assert!(parsed.is_some());
|
||||
let parsed = parsed.unwrap_or_else(|| unreachable!());
|
||||
|
||||
assert!(parsed.serve);
|
||||
assert!(parsed.command.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_import_certs_with_zip_defaults() {
|
||||
let parsed = Cli::try_parse_from(["nxmesh-agent", "import-certs", "bundle.zip"]);
|
||||
assert!(parsed.is_ok());
|
||||
|
||||
let parsed = parsed.ok();
|
||||
assert!(parsed.is_some());
|
||||
let parsed = parsed.unwrap_or_else(|| unreachable!());
|
||||
|
||||
match parsed.command {
|
||||
Some(Commands::ImportCerts {
|
||||
zip,
|
||||
cert_name,
|
||||
key_name,
|
||||
ca_name,
|
||||
cert,
|
||||
key,
|
||||
ca_cert,
|
||||
}) => {
|
||||
assert_eq!(zip.as_deref(), Some("bundle.zip"));
|
||||
assert_eq!(cert_name.as_deref(), Some("cert.pem"));
|
||||
assert_eq!(key_name.as_deref(), Some("key.pem"));
|
||||
assert_eq!(ca_name.as_deref(), Some("ca.pem"));
|
||||
assert!(cert.is_none());
|
||||
assert!(key.is_none());
|
||||
assert!(ca_cert.is_none());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_import_certs_with_separate_files() {
|
||||
let parsed = Cli::try_parse_from([
|
||||
"nxmesh-agent",
|
||||
"import-certs",
|
||||
"--cert",
|
||||
"agent.crt",
|
||||
"--key",
|
||||
"agent.key",
|
||||
"--ca-cert",
|
||||
"ca.crt",
|
||||
]);
|
||||
assert!(parsed.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_conflicting_zip_and_separate_inputs() {
|
||||
let parsed = Cli::try_parse_from([
|
||||
"nxmesh-agent",
|
||||
"import-certs",
|
||||
"bundle.zip",
|
||||
"--cert",
|
||||
"agent.crt",
|
||||
"--key",
|
||||
"agent.key",
|
||||
]);
|
||||
|
||||
assert!(parsed.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +318,14 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
fs,
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use tempfile::TempDir;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -330,4 +338,223 @@ mod tests {
|
||||
assert_send_sync::<LogSettings>();
|
||||
assert_send_sync::<NginxSettings>();
|
||||
}
|
||||
|
||||
fn write_file(path: &Path) {
|
||||
let result = fs::write(path, b"content");
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
fn create_exec_file(path: &Path) {
|
||||
write_file(path);
|
||||
let metadata = fs::metadata(path);
|
||||
assert!(metadata.is_ok());
|
||||
let metadata = metadata.ok();
|
||||
assert!(metadata.is_some());
|
||||
let metadata = metadata.unwrap_or_else(|| unreachable!());
|
||||
|
||||
let mut perms = metadata.permissions();
|
||||
perms.set_mode(0o755);
|
||||
let result = fs::set_permissions(path, perms);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
fn create_non_exec_file(path: &Path) {
|
||||
write_file(path);
|
||||
let metadata = fs::metadata(path);
|
||||
assert!(metadata.is_ok());
|
||||
let metadata = metadata.ok();
|
||||
assert!(metadata.is_some());
|
||||
let metadata = metadata.unwrap_or_else(|| unreachable!());
|
||||
|
||||
let mut perms = metadata.permissions();
|
||||
perms.set_mode(0o644);
|
||||
let result = fs::set_permissions(path, perms);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
fn valid_tls_raw_paths(temp_dir: &TempDir) -> (PathBuf, PathBuf, PathBuf) {
|
||||
let ca_path = temp_dir.path().join("ca.pem");
|
||||
let cert_path = temp_dir.path().join("cert.pem");
|
||||
let key_path = temp_dir.path().join("key.pem");
|
||||
|
||||
write_file(&ca_path);
|
||||
write_file(&cert_path);
|
||||
write_file(&key_path);
|
||||
|
||||
(ca_path, cert_path, key_path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tls_raw_path_validate_succeeds_when_all_files_exist() {
|
||||
let temp_dir = TempDir::new();
|
||||
assert!(temp_dir.is_ok());
|
||||
let temp_dir = temp_dir.ok();
|
||||
assert!(temp_dir.is_some());
|
||||
let temp_dir = temp_dir.unwrap_or_else(|| unreachable!());
|
||||
|
||||
let (ca_path, cert_path, key_path) = valid_tls_raw_paths(&temp_dir);
|
||||
let settings = TLSSettings::RawPath {
|
||||
ca_path: ca_path.to_string_lossy().to_string(),
|
||||
cert_path: cert_path.to_string_lossy().to_string(),
|
||||
key_path: key_path.to_string_lossy().to_string(),
|
||||
};
|
||||
|
||||
assert!(settings.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tls_raw_path_validate_fails_when_ca_missing() {
|
||||
let settings = TLSSettings::RawPath {
|
||||
ca_path: "/tmp/does-not-exist-ca.pem".into(),
|
||||
cert_path: "/tmp/does-not-exist-cert.pem".into(),
|
||||
key_path: "/tmp/does-not-exist-key.pem".into(),
|
||||
};
|
||||
|
||||
let result = settings.validate();
|
||||
assert!(result.is_err());
|
||||
let msg = result.err().unwrap_or_else(|| unreachable!());
|
||||
assert!(msg.contains("CA file not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tls_zip_path_validate_fails_when_zip_missing() {
|
||||
let settings = TLSSettings::ZipPath {
|
||||
cert_zip_path: "/tmp/missing-certs.zip".into(),
|
||||
};
|
||||
|
||||
let result = settings.validate();
|
||||
assert!(result.is_err());
|
||||
let msg = result.err().unwrap_or_else(|| unreachable!());
|
||||
assert!(msg.contains("Certificate zip file not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grpc_validate_fails_when_connection_string_empty() {
|
||||
let settings = GrpcSettings {
|
||||
connection_string: "".into(),
|
||||
m_auth: MAuthSettings::Tls(TLSSettings::ZipPath {
|
||||
cert_zip_path: "/tmp/does-not-exist.zip".into(),
|
||||
}),
|
||||
cors: None,
|
||||
};
|
||||
|
||||
let result = settings.validate();
|
||||
assert!(result.is_err());
|
||||
let msg = result.err().unwrap_or_else(|| unreachable!());
|
||||
assert!(msg.contains("gRPC connection string cannot be empty"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nginx_validate_succeeds_for_valid_paths_and_commands() {
|
||||
let temp_dir = TempDir::new();
|
||||
assert!(temp_dir.is_ok());
|
||||
let temp_dir = temp_dir.ok();
|
||||
assert!(temp_dir.is_some());
|
||||
let temp_dir = temp_dir.unwrap_or_else(|| unreachable!());
|
||||
|
||||
let nginx_binary = temp_dir.path().join("nginx");
|
||||
let nginx_config = temp_dir.path().join("nginx.conf");
|
||||
|
||||
create_exec_file(&nginx_binary);
|
||||
write_file(&nginx_config);
|
||||
|
||||
let nginx = NginxSettings {
|
||||
nginx_config_path: nginx_config.to_string_lossy().to_string(),
|
||||
nginx_binary_path: Some(nginx_binary.to_string_lossy().to_string()),
|
||||
override_nginx_reload_command: default_nginx_reload_command(),
|
||||
override_nginx_test_command: default_nginx_test_command(),
|
||||
nginx_reload_timeout_seconds: 30,
|
||||
nginx_test_timeout_seconds: 30,
|
||||
};
|
||||
|
||||
assert!(nginx.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nginx_validate_fails_for_non_executable_binary() {
|
||||
let temp_dir = TempDir::new();
|
||||
assert!(temp_dir.is_ok());
|
||||
let temp_dir = temp_dir.ok();
|
||||
assert!(temp_dir.is_some());
|
||||
let temp_dir = temp_dir.unwrap_or_else(|| unreachable!());
|
||||
|
||||
let nginx_binary = temp_dir.path().join("nginx");
|
||||
let nginx_config = temp_dir.path().join("nginx.conf");
|
||||
|
||||
create_non_exec_file(&nginx_binary);
|
||||
write_file(&nginx_config);
|
||||
|
||||
let nginx = NginxSettings {
|
||||
nginx_config_path: nginx_config.to_string_lossy().to_string(),
|
||||
nginx_binary_path: Some(nginx_binary.to_string_lossy().to_string()),
|
||||
override_nginx_reload_command: default_nginx_reload_command(),
|
||||
override_nginx_test_command: default_nginx_test_command(),
|
||||
nginx_reload_timeout_seconds: 30,
|
||||
nginx_test_timeout_seconds: 30,
|
||||
};
|
||||
|
||||
let result = nginx.validate();
|
||||
assert!(result.is_err());
|
||||
let msg = result.err().unwrap_or_else(|| unreachable!());
|
||||
assert!(msg.contains("Nginx binary is not executable"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nginx_validate_fails_when_reload_command_lacks_template() {
|
||||
let temp_dir = TempDir::new();
|
||||
assert!(temp_dir.is_ok());
|
||||
let temp_dir = temp_dir.ok();
|
||||
assert!(temp_dir.is_some());
|
||||
let temp_dir = temp_dir.unwrap_or_else(|| unreachable!());
|
||||
|
||||
let nginx_binary = temp_dir.path().join("nginx");
|
||||
let nginx_config = temp_dir.path().join("nginx.conf");
|
||||
|
||||
create_exec_file(&nginx_binary);
|
||||
write_file(&nginx_config);
|
||||
|
||||
let nginx = NginxSettings {
|
||||
nginx_config_path: nginx_config.to_string_lossy().to_string(),
|
||||
nginx_binary_path: Some(nginx_binary.to_string_lossy().to_string()),
|
||||
override_nginx_reload_command: vec!["nginx".into(), "-s".into(), "reload".into()],
|
||||
override_nginx_test_command: default_nginx_test_command(),
|
||||
nginx_reload_timeout_seconds: 30,
|
||||
nginx_test_timeout_seconds: 30,
|
||||
};
|
||||
|
||||
let result = nginx.validate();
|
||||
assert!(result.is_err());
|
||||
let msg = result.err().unwrap_or_else(|| unreachable!());
|
||||
assert!(msg.contains("Nginx reload command must contain the binary path template"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn level_filter_round_trip_serialization() {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Wrapper {
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_level_filter",
|
||||
serialize_with = "serialize_level_filter"
|
||||
)]
|
||||
level: LevelFilter,
|
||||
}
|
||||
|
||||
let original = Wrapper {
|
||||
level: LevelFilter::DEBUG,
|
||||
};
|
||||
|
||||
let encoded = serde_json::to_string(&original);
|
||||
assert!(encoded.is_ok());
|
||||
let encoded = encoded.ok();
|
||||
assert!(encoded.is_some());
|
||||
let encoded = encoded.unwrap_or_else(|| unreachable!());
|
||||
assert!(encoded.to_lowercase().contains("debug"));
|
||||
|
||||
let decoded = serde_json::from_str::<Wrapper>(&encoded);
|
||||
assert!(decoded.is_ok());
|
||||
let decoded = decoded.ok();
|
||||
assert!(decoded.is_some());
|
||||
let decoded = decoded.unwrap_or_else(|| unreachable!());
|
||||
assert_eq!(decoded.level, LevelFilter::DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ use tokio::sync::Mutex;
|
||||
|
||||
pub mod ssh;
|
||||
|
||||
pub type AgentClient = nxmesh_proto::agent_service_client::AgentServiceClient<tonic::transport::Channel>;
|
||||
pub type AgentClient =
|
||||
nxmesh_proto::agent_service_client::AgentServiceClient<tonic::transport::Channel>;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait MasterConnectorTrait: Send + Sync {
|
||||
@@ -38,3 +39,105 @@ impl MasterConnectorTrait for MasterConnector {
|
||||
self.connector.get_client()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::config::settings::{
|
||||
GrpcSettings, LogSettings, MAuthSettings, Settings, TLSSettings,
|
||||
};
|
||||
|
||||
use super::{AgentClient, MasterConnector, MasterConnectorTrait};
|
||||
|
||||
struct FakeConnector {
|
||||
called: Arc<AtomicBool>,
|
||||
fail: bool,
|
||||
client: Arc<Mutex<AgentClient>>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MasterConnectorTrait for FakeConnector {
|
||||
async fn connect(
|
||||
&mut self,
|
||||
_settings: &Settings,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
self.called.store(true, Ordering::SeqCst);
|
||||
if self.fail {
|
||||
return Err("connector failed".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_client(&self) -> Arc<Mutex<AgentClient>> {
|
||||
self.client.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn test_settings() -> Settings {
|
||||
Settings {
|
||||
grpc: GrpcSettings {
|
||||
connection_string: "https://localhost:50051".to_string(),
|
||||
m_auth: MAuthSettings::Tls(TLSSettings::ZipPath {
|
||||
cert_zip_path: "/tmp/certs.zip".to_string(),
|
||||
}),
|
||||
cors: None,
|
||||
},
|
||||
log: LogSettings::default(),
|
||||
nginx: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn test_client() -> Arc<Mutex<AgentClient>> {
|
||||
let channel =
|
||||
tonic::transport::Channel::from_static("http://127.0.0.1:50051").connect_lazy();
|
||||
Arc::new(Mutex::new(AgentClient::new(channel)))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn master_connector_delegates_connect_successfully() {
|
||||
let called = Arc::new(AtomicBool::new(false));
|
||||
let fake = FakeConnector {
|
||||
called: called.clone(),
|
||||
fail: false,
|
||||
client: test_client(),
|
||||
};
|
||||
let mut master = MasterConnector::new(Box::new(fake));
|
||||
|
||||
let result = master.connect(&test_settings()).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(called.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn master_connector_propagates_connect_errors() {
|
||||
let fake = FakeConnector {
|
||||
called: Arc::new(AtomicBool::new(false)),
|
||||
fail: true,
|
||||
client: test_client(),
|
||||
};
|
||||
let mut master = MasterConnector::new(Box::new(fake));
|
||||
|
||||
let result = master.connect(&test_settings()).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn master_connector_returns_underlying_client() {
|
||||
let shared_client = test_client();
|
||||
let fake = FakeConnector {
|
||||
called: Arc::new(AtomicBool::new(false)),
|
||||
fail: false,
|
||||
client: shared_client.clone(),
|
||||
};
|
||||
let master = MasterConnector::new(Box::new(fake));
|
||||
|
||||
let client = master.get_client();
|
||||
assert!(Arc::ptr_eq(&client, &shared_client));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use nxmesh_proto::agent_service_client::AgentServiceClient;
|
||||
use tonic::transport::{Certificate, ClientTlsConfig, Identity};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::config::settings::{self, MAuthSettings, TLSSettings};
|
||||
use crate::config::settings::{MAuthSettings, TLSSettings};
|
||||
|
||||
use super::{AgentClient, MasterConnectorTrait};
|
||||
|
||||
@@ -130,3 +130,151 @@ impl MasterConnectorTrait for SshMasterConnector {
|
||||
self.client.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::expect_used)]
|
||||
mod tests {
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use tempfile::TempDir;
|
||||
|
||||
use crate::config::settings::{MAuthSettings, TLSSettings};
|
||||
|
||||
use super::SshMasterConnector;
|
||||
|
||||
const CERT_PEM: &[u8] = b"-----BEGIN CERTIFICATE-----\nAQ==\n-----END CERTIFICATE-----\n";
|
||||
const KEY_PEM: &[u8] = b"-----BEGIN PRIVATE KEY-----\nAQ==\n-----END PRIVATE KEY-----\n";
|
||||
const CA_PEM: &[u8] = b"-----BEGIN CERTIFICATE-----\nAQ==\n-----END CERTIFICATE-----\n";
|
||||
|
||||
fn create_zip_with_entries(
|
||||
dir: &TempDir,
|
||||
file_name: &str,
|
||||
entries: &[(&str, &[u8])],
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let zip_path = dir.path().join(file_name);
|
||||
let file = File::create(&zip_path)?;
|
||||
let mut zip = zip::ZipWriter::new(file);
|
||||
let options = zip::write::SimpleFileOptions::default()
|
||||
.compression_method(zip::CompressionMethod::Deflated)
|
||||
.unix_permissions(0o600);
|
||||
|
||||
for (entry_name, contents) in entries {
|
||||
zip.start_file(entry_name, options)?;
|
||||
zip.write_all(contents)?;
|
||||
}
|
||||
|
||||
zip.finish()?;
|
||||
Ok(zip_path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
fn write_file(
|
||||
path: &Path,
|
||||
contents: &[u8],
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
fs::write(path, contents)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn extract_certificate_succeeds_with_expected_files() {
|
||||
let temp_dir = TempDir::new().expect("failed to create temp dir");
|
||||
let zip_path = create_zip_with_entries(
|
||||
&temp_dir,
|
||||
"certs.zip",
|
||||
&[
|
||||
("cert.pem", CERT_PEM),
|
||||
("key.pem", KEY_PEM),
|
||||
("ca.pem", CA_PEM),
|
||||
("ignored.txt", b"ignored"),
|
||||
],
|
||||
)
|
||||
.expect("failed to create zip");
|
||||
|
||||
let (ca, cert, key) = SshMasterConnector::extract_certificate(&zip_path)
|
||||
.await
|
||||
.expect("expected cert extraction to succeed");
|
||||
|
||||
assert_eq!(ca, CA_PEM);
|
||||
assert_eq!(cert, CERT_PEM);
|
||||
assert_eq!(key, KEY_PEM);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn extract_certificate_fails_when_required_files_are_missing() {
|
||||
let temp_dir = TempDir::new().expect("failed to create temp dir");
|
||||
let zip_path = create_zip_with_entries(
|
||||
&temp_dir,
|
||||
"missing-key.zip",
|
||||
&[("cert.pem", CERT_PEM), ("ca.pem", CA_PEM)],
|
||||
)
|
||||
.expect("failed to create zip");
|
||||
|
||||
let err = SshMasterConnector::extract_certificate(&zip_path)
|
||||
.await
|
||||
.expect_err("expected extraction to fail when key.pem is missing");
|
||||
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("Certificate zip must contain cert.pem, key.pem and ca.pem")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn generate_tls_config_succeeds_for_raw_paths() {
|
||||
let temp_dir = TempDir::new().expect("failed to create temp dir");
|
||||
let cert_path = temp_dir.path().join("cert.pem");
|
||||
let key_path = temp_dir.path().join("key.pem");
|
||||
let ca_path = temp_dir.path().join("ca.pem");
|
||||
|
||||
write_file(&cert_path, CERT_PEM).expect("failed to write cert.pem");
|
||||
write_file(&key_path, KEY_PEM).expect("failed to write key.pem");
|
||||
write_file(&ca_path, CA_PEM).expect("failed to write ca.pem");
|
||||
|
||||
let settings = MAuthSettings::Tls(TLSSettings::RawPath {
|
||||
ca_path: ca_path.to_string_lossy().to_string(),
|
||||
cert_path: cert_path.to_string_lossy().to_string(),
|
||||
key_path: key_path.to_string_lossy().to_string(),
|
||||
});
|
||||
|
||||
let result = SshMasterConnector::generate_tls_config(&settings).await;
|
||||
assert!(result.is_ok(), "expected raw path TLS config to succeed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn generate_tls_config_succeeds_for_zip_path() {
|
||||
let temp_dir = TempDir::new().expect("failed to create temp dir");
|
||||
let zip_path = create_zip_with_entries(
|
||||
&temp_dir,
|
||||
"certs.zip",
|
||||
&[
|
||||
("cert.pem", CERT_PEM),
|
||||
("key.pem", KEY_PEM),
|
||||
("ca.pem", CA_PEM),
|
||||
],
|
||||
)
|
||||
.expect("failed to create zip");
|
||||
|
||||
let settings = MAuthSettings::Tls(TLSSettings::ZipPath {
|
||||
cert_zip_path: zip_path,
|
||||
});
|
||||
|
||||
let result = SshMasterConnector::generate_tls_config(&settings).await;
|
||||
assert!(result.is_ok(), "expected zip path TLS config to succeed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn generate_tls_config_fails_for_missing_raw_files() {
|
||||
let settings = MAuthSettings::Tls(TLSSettings::RawPath {
|
||||
ca_path: "/tmp/non-existent-ca.pem".to_string(),
|
||||
cert_path: "/tmp/non-existent-cert.pem".to_string(),
|
||||
key_path: "/tmp/non-existent-key.pem".to_string(),
|
||||
});
|
||||
|
||||
let result = SshMasterConnector::generate_tls_config(&settings).await;
|
||||
assert!(result.is_err(), "expected raw path TLS config to fail");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user